@planningcenter/chat-react-native 4.4.1-rc.0 → 4.5.0-rc.1

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 (64) hide show
  1. package/build/components/conversation/message.d.ts.map +1 -1
  2. package/build/components/conversation/message.js +193 -145
  3. package/build/components/conversation/message.js.map +1 -1
  4. package/build/components/conversation/reply_shadow_message.d.ts.map +1 -1
  5. package/build/components/conversation/reply_shadow_message.js +135 -107
  6. package/build/components/conversation/reply_shadow_message.js.map +1 -1
  7. package/build/components/conversations/conversations.d.ts.map +1 -1
  8. package/build/components/conversations/conversations.js +3 -2
  9. package/build/components/conversations/conversations.js.map +1 -1
  10. package/build/contexts/conversations_context.d.ts.map +1 -1
  11. package/build/contexts/conversations_context.js +20 -17
  12. package/build/contexts/conversations_context.js.map +1 -1
  13. package/build/hooks/use_clear_conversation_filters.d.ts.map +1 -1
  14. package/build/hooks/use_clear_conversation_filters.js +2 -1
  15. package/build/hooks/use_clear_conversation_filters.js.map +1 -1
  16. package/build/hooks/use_conversations_cache.d.ts +1 -1
  17. package/build/hooks/use_conversations_cache.d.ts.map +1 -1
  18. package/build/hooks/use_conversations_cache.js +64 -55
  19. package/build/hooks/use_conversations_cache.js.map +1 -1
  20. package/build/hooks/use_conversations_jolt_events.js +2 -2
  21. package/build/hooks/use_conversations_jolt_events.js.map +1 -1
  22. package/build/hooks/use_jolt.d.ts.map +1 -1
  23. package/build/hooks/use_jolt.js +37 -9
  24. package/build/hooks/use_jolt.js.map +1 -1
  25. package/build/screens/conversation_filters/context/conversation_filter_context.d.ts.map +1 -1
  26. package/build/screens/conversation_filters/context/conversation_filter_context.js +8 -4
  27. package/build/screens/conversation_filters/context/conversation_filter_context.js.map +1 -1
  28. package/build/screens/conversation_filters/screen_props.d.ts +1 -0
  29. package/build/screens/conversation_filters/screen_props.d.ts.map +1 -1
  30. package/build/screens/conversation_filters/screen_props.js.map +1 -1
  31. package/build/screens/conversations/components/filter_dropdown_list_header_component.d.ts.map +1 -1
  32. package/build/screens/conversations/components/filter_dropdown_list_header_component.js +191 -168
  33. package/build/screens/conversations/components/filter_dropdown_list_header_component.js.map +1 -1
  34. package/build/screens/conversations/conversations_screen.d.ts +1 -0
  35. package/build/screens/conversations/conversations_screen.d.ts.map +1 -1
  36. package/build/screens/conversations/conversations_screen.js.map +1 -1
  37. package/build/utils/request/conversation.d.ts +2 -1
  38. package/build/utils/request/conversation.d.ts.map +1 -1
  39. package/build/utils/request/conversation.js +4 -1
  40. package/build/utils/request/conversation.js.map +1 -1
  41. package/build/utils/styles.d.ts +1 -0
  42. package/build/utils/styles.d.ts.map +1 -1
  43. package/build/utils/styles.js +1 -1
  44. package/build/utils/styles.js.map +1 -1
  45. package/package.json +2 -2
  46. package/src/components/conversation/message.tsx +10 -2
  47. package/src/components/conversation/reply_shadow_message.tsx +14 -3
  48. package/src/components/conversations/__tests__/conversations_blank_state.test.tsx +1 -0
  49. package/src/components/conversations/conversations.tsx +12 -2
  50. package/src/contexts/conversations_context.tsx +8 -1
  51. package/src/hooks/__tests__/use_conversations_jolt_events.test.tsx +135 -0
  52. package/src/hooks/__tests__/use_jolt.test.tsx +76 -1
  53. package/src/hooks/use_clear_conversation_filters.ts +1 -0
  54. package/src/hooks/use_conversations_cache.ts +29 -24
  55. package/src/hooks/use_conversations_jolt_events.ts +2 -2
  56. package/src/hooks/use_jolt.ts +26 -2
  57. package/src/screens/conversation_filters/context/conversation_filter_context.tsx +4 -0
  58. package/src/screens/conversation_filters/screen_props.ts +1 -0
  59. package/src/screens/conversations/components/__tests__/filter_dropdown_list_header_component.test.tsx +55 -3
  60. package/src/screens/conversations/components/filter_dropdown_list_header_component.tsx +17 -2
  61. package/src/screens/conversations/conversations_screen.tsx +1 -0
  62. package/src/utils/__tests__/conversation.test.ts +1 -0
  63. package/src/utils/request/conversation.ts +4 -0
  64. package/src/utils/styles.ts +1 -0
@@ -9,7 +9,7 @@ import { ChatContext, ChatContextValue } from '../../contexts/chat_context'
9
9
  import { Session } from '../../utils'
10
10
  import { Client } from '../../utils/client'
11
11
  import { ResponseError } from '../../utils/response_error'
12
- import { useJoltClient, useJoltForegroundPing } from '../use_jolt'
12
+ import { useJoltChannel, useJoltClient, useJoltEvent, useJoltForegroundPing } from '../use_jolt'
13
13
 
14
14
  let caught: unknown
15
15
 
@@ -167,3 +167,78 @@ describe('useJoltForegroundPing', () => {
167
167
  expect(ping).toHaveBeenCalledTimes(1)
168
168
  })
169
169
  })
170
+
171
+ describe('useJoltChannel', () => {
172
+ type Listener = (_event: unknown) => void
173
+ const channelName = 'chat.people.1'
174
+
175
+ const buildJolt = () => {
176
+ const listeners: Listener[] = []
177
+ const subscription = {
178
+ channel: channelName,
179
+ bind: jest.fn((_event: string, listener: Listener) => {
180
+ listeners.push(listener)
181
+ return () => listeners.splice(listeners.indexOf(listener), 1)
182
+ }),
183
+ }
184
+ const jolt = {
185
+ subscriptions: [] as (typeof subscription)[],
186
+ subscribe: jest.fn(() => {
187
+ jolt.subscriptions.push(subscription)
188
+ return subscription
189
+ }),
190
+ // The real client unbinds every listener on the subscription when it unsubscribes.
191
+ unsubscribe: jest.fn(() => {
192
+ listeners.length = 0
193
+ jolt.subscriptions = []
194
+ }),
195
+ }
196
+
197
+ return { jolt, emit: (event: unknown) => listeners.forEach(listener => listener(event)) }
198
+ }
199
+
200
+ const ChannelProbe = ({ onEvent }: { onEvent: Listener }) => {
201
+ const channel = useJoltChannel(channelName)
202
+ useJoltEvent(channel, 'conversation.updated', onEvent)
203
+ return null
204
+ }
205
+
206
+ const renderConsumers = (jolt: unknown, onEvent: Listener) => {
207
+ const session = new Session({ token: { access_token: 'a-real-token' } })
208
+ const queryClient = buildTestQueryClient({ defaultOptions: { queries: { gcTime: Infinity } } })
209
+ queryClient.setQueryData(['wss-url'], 'wss://jolt.test')
210
+ queryClient.setQueryData(['jolt-client', 'wss://jolt.test', 'a-real-token'], jolt)
211
+
212
+ const tree = (count: number) => (
213
+ <QueryClientProvider client={queryClient}>
214
+ <ChatContext.Provider value={{ session } as unknown as ChatContextValue}>
215
+ <Suspense fallback={null}>
216
+ {Array.from({ length: count }, (_, index) => (
217
+ <ChannelProbe key={index} onEvent={onEvent} />
218
+ ))}
219
+ </Suspense>
220
+ </ChatContext.Provider>
221
+ </QueryClientProvider>
222
+ )
223
+ const result = render(tree(2))
224
+
225
+ return { setConsumers: (count: number) => result.rerender(tree(count)) }
226
+ }
227
+
228
+ it('keeps delivering events to the remaining consumer after another unmounts', async () => {
229
+ const { jolt, emit } = buildJolt()
230
+ const onEvent = jest.fn()
231
+ const { setConsumers } = renderConsumers(jolt, onEvent)
232
+ await waitFor(() => expect(jolt.subscribe).toHaveBeenCalledTimes(1))
233
+
234
+ setConsumers(1)
235
+ emit({ event: 'conversation.updated' })
236
+
237
+ expect(jolt.unsubscribe).not.toHaveBeenCalled()
238
+ expect(onEvent).toHaveBeenCalledTimes(1)
239
+
240
+ setConsumers(0)
241
+
242
+ expect(jolt.unsubscribe).toHaveBeenCalledWith(channelName)
243
+ })
244
+ })
@@ -11,6 +11,7 @@ export function useClearConversationFilters() {
11
11
  group_source_app_name: undefined,
12
12
  show_direct_messages: undefined,
13
13
  show_archived: undefined,
14
+ show_unread: undefined,
14
15
  })
15
16
  }, [navigation])
16
17
  }
@@ -1,5 +1,5 @@
1
1
  import { InfiniteData, useQueryClient } from '@tanstack/react-query'
2
- import { ApiCollection, ApiResource, ConversationResource } from '../types'
2
+ import { ApiCollection, ConversationResource } from '../types'
3
3
  import {
4
4
  deleteRecordInPagesData,
5
5
  updateRecordInPagesData,
@@ -21,16 +21,13 @@ export function useConversationsCache(args?: Partial<ConversationRequestArgs>) {
21
21
  const fetchConversation = async (id: number) => {
22
22
  if (!id) return
23
23
 
24
- const { data: argsData } = conversationsRequestArgs
25
- const { data } = await apiClient.chat.get<ApiResource<ConversationResource>>({
26
- url: `/me/conversations/${id}`,
27
- data: {
28
- fields: argsData.fields,
29
- include: argsData.include,
30
- },
24
+ const { url, data } = conversationsRequestArgs
25
+ const { data: conversations } = await apiClient.chat.get<ApiCollection<ConversationResource>>({
26
+ url,
27
+ data: { ...data, where: { ...data.where, id }, perPage: 1 },
31
28
  })
32
29
 
33
- return data
30
+ return conversations[0]
34
31
  }
35
32
 
36
33
  const updateOrCreate = async (conversation: ConversationResource) => {
@@ -70,20 +67,6 @@ export function useConversationsCache(args?: Partial<ConversationRequestArgs>) {
70
67
  )
71
68
  }
72
69
 
73
- const fetchAndUpdate = async ({ id }: { id: number }) => {
74
- const conversation: ConversationResource = await fetchConversation(id).catch(c => c)
75
- if (!conversation.id) return
76
-
77
- updateOnly(conversation)
78
- }
79
-
80
- const fetchAndUpdateOrCreate = async ({ id }: { id: number }) => {
81
- const conversation: ConversationResource = await fetchConversation(id).catch(c => c)
82
- if (!conversation.id) return
83
-
84
- updateOrCreate(conversation)
85
- }
86
-
87
70
  const handleConversationDestroy = ({ id }: { id: number }) => {
88
71
  if (!id) return
89
72
 
@@ -95,10 +78,32 @@ export function useConversationsCache(args?: Partial<ConversationRequestArgs>) {
95
78
  )
96
79
  }
97
80
 
81
+ const fetchAndUpdate = async ({ id }: { id: number }) => {
82
+ const conversation = await fetchConversation(id).catch(() => undefined)
83
+ if (!conversation) return
84
+
85
+ updateOnly(conversation)
86
+ }
87
+
88
+ const fetchAndSync = async ({ id }: { id: number }) => {
89
+ let conversation: ConversationResource | undefined
90
+ try {
91
+ conversation = await fetchConversation(id)
92
+ } catch {
93
+ return
94
+ }
95
+
96
+ if (conversation) {
97
+ updateOrCreate(conversation)
98
+ } else {
99
+ handleConversationDestroy({ id })
100
+ }
101
+ }
102
+
98
103
  return {
99
104
  create: updateOrCreate,
100
105
  destroy: handleConversationDestroy,
101
- fetchCreate: fetchAndUpdateOrCreate,
106
+ fetchSync: fetchAndSync,
102
107
  fetchUpdate: fetchAndUpdate,
103
108
  invalidate: () => queryClient.invalidateQueries({ queryKey: conversationQueryKey }),
104
109
  update: updateOrCreate,
@@ -31,10 +31,10 @@ export function useConversationsJoltEvents() {
31
31
  idempotentKey: e.data.data.last_message_idempotent_key,
32
32
  })
33
33
  }
34
- cache.fetchUpdate({ id: e.data.data.id })
34
+ cache.fetchSync({ id: e.data.data.id })
35
35
  })
36
36
  useJoltEvent(joltChannel, 'conversation.created', (e: JoltConversationEvent) =>
37
- cache.fetchCreate({ id: e.data.data.id })
37
+ cache.fetchSync({ id: e.data.data.id })
38
38
  )
39
39
  useJoltEvent(joltChannel, 'conversation.destroyed', (e: JoltConversationEvent) =>
40
40
  cache.destroy({ id: e.data.data.id })
@@ -181,6 +181,25 @@ export const useJoltForegroundPing = () => {
181
181
  }, [queryClient])
182
182
  }
183
183
 
184
+ const channelConsumers = new WeakMap<JoltClient, Map<string, number>>()
185
+
186
+ const retainChannel = (jolt: JoltClient, channelName: string) => {
187
+ const counts = channelConsumers.get(jolt) ?? new Map<string, number>()
188
+ channelConsumers.set(jolt, counts)
189
+ counts.set(channelName, (counts.get(channelName) ?? 0) + 1)
190
+ }
191
+
192
+ const releaseChannel = (jolt: JoltClient, channelName: string) => {
193
+ const counts = channelConsumers.get(jolt)
194
+ const remaining = (counts?.get(channelName) ?? 1) - 1
195
+ if (remaining > 0) {
196
+ counts?.set(channelName, remaining)
197
+ } else {
198
+ counts?.delete(channelName)
199
+ }
200
+ return remaining
201
+ }
202
+
184
203
  export function useJoltChannel(
185
204
  channelName: string,
186
205
  enabled: boolean = true
@@ -216,8 +235,13 @@ export function useJoltChannel(
216
235
  }, [queryClient, channelName, jolt])
217
236
 
218
237
  useEffect(() => {
219
- return handleUnsubscribe
220
- }, [handleUnsubscribe])
238
+ if (!jolt) return
239
+
240
+ retainChannel(jolt, channelName)
241
+ return () => {
242
+ if (releaseChannel(jolt, channelName) === 0) handleUnsubscribe()
243
+ }
244
+ }, [jolt, channelName, handleUnsubscribe])
221
245
 
222
246
  return subscription
223
247
  }
@@ -23,6 +23,7 @@ export const FilterContext = createContext<{
23
23
  group_source_app_name: undefined,
24
24
  show_direct_messages: undefined,
25
25
  show_archived: undefined,
26
+ show_unread: undefined,
26
27
  },
27
28
  scrollOffset: 0,
28
29
  setScrollOffset: () => {},
@@ -59,6 +60,7 @@ export const FilterProvider = ({ children }: PropsWithChildren) => {
59
60
  group_source_app_name: undefined,
60
61
  show_direct_messages: undefined,
61
62
  show_archived: undefined,
63
+ show_unread: undefined,
62
64
  })
63
65
  )
64
66
  },
@@ -68,6 +70,7 @@ export const FilterProvider = ({ children }: PropsWithChildren) => {
68
70
  group_source_app_name: undefined,
69
71
  show_direct_messages: undefined,
70
72
  show_archived: undefined,
73
+ show_unread: undefined,
71
74
  })
72
75
  },
73
76
  setAppFilter: (params: Omit<ConversationFiltersParams, 'chat_group_graph_id'>) => {
@@ -76,6 +79,7 @@ export const FilterProvider = ({ children }: PropsWithChildren) => {
76
79
  group_source_app_name: params.group_source_app_name,
77
80
  show_direct_messages: params.show_direct_messages,
78
81
  show_archived: params.show_archived,
82
+ show_unread: undefined,
79
83
  })
80
84
  },
81
85
  applyFilters: (params: ConversationFiltersParams) => {
@@ -6,6 +6,7 @@ export type ConversationFiltersParams = {
6
6
  group_source_app_name?: string
7
7
  show_direct_messages?: boolean
8
8
  show_archived?: boolean
9
+ show_unread?: boolean
9
10
  }
10
11
 
11
12
  export type ConversationFiltersScreenProps = StaticScreenProps<ConversationFiltersParams>
@@ -128,7 +128,7 @@ describe('FilterDropdownListHeaderComponent', () => {
128
128
  it('offers every filter the person can use, then More filters', () => {
129
129
  render(<FilterDropdownListHeaderComponent />)
130
130
 
131
- expect(menuItemNames()).toEqual(['All', 'Groups', 'Teams', 'DMs', 'More filters'])
131
+ expect(menuItemNames()).toEqual(['All', 'Groups', 'Teams', 'DMs', 'Unread', 'More filters'])
132
132
  expect(screen.getByRole('button', { name: 'All conversations' })).toBeTruthy()
133
133
  })
134
134
 
@@ -137,7 +137,7 @@ describe('FilterDropdownListHeaderComponent', () => {
137
137
 
138
138
  render(<FilterDropdownListHeaderComponent />)
139
139
 
140
- expect(menuItemNames()).toEqual(['All', 'DMs'])
140
+ expect(menuItemNames()).toEqual(['All', 'DMs', 'Unread'])
141
141
  })
142
142
 
143
143
  it('selecting Groups filters to group conversations', () => {
@@ -150,6 +150,45 @@ describe('FilterDropdownListHeaderComponent', () => {
150
150
  )
151
151
  })
152
152
 
153
+ it('selecting Unread filters to unread conversations', () => {
154
+ setRouteParams({ group_source_app_name: 'Groups' })
155
+
156
+ render(<FilterDropdownListHeaderComponent />)
157
+
158
+ fireEvent.press(screen.getByRole('menuitem', { name: 'Unread' }))
159
+
160
+ expect(mockSetParams).toHaveBeenCalledWith({
161
+ title: undefined,
162
+ chat_group_graph_id: undefined,
163
+ group_source_app_name: undefined,
164
+ show_direct_messages: undefined,
165
+ show_unread: true,
166
+ })
167
+ })
168
+
169
+ it('selecting another filter clears Unread', () => {
170
+ setRouteParams({ show_unread: true })
171
+
172
+ render(<FilterDropdownListHeaderComponent />)
173
+
174
+ fireEvent.press(screen.getByRole('menuitem', { name: 'DMs' }))
175
+
176
+ expect(mockSetParams).toHaveBeenCalledWith(
177
+ expect.objectContaining({ show_direct_messages: true, show_unread: undefined })
178
+ )
179
+ })
180
+
181
+ it('labels the trigger Unread and checks it when filtered to unread', () => {
182
+ setRouteParams({ show_unread: true })
183
+
184
+ render(<FilterDropdownListHeaderComponent />)
185
+
186
+ expect(screen.getByRole('button', { name: 'Unread' })).toBeTruthy()
187
+ expect(screen.getByRole('menuitem', { name: 'Unread' }).props.accessibilityState.selected).toBe(
188
+ true
189
+ )
190
+ })
191
+
153
192
  it('labels the trigger with the active filter and checks it', () => {
154
193
  setRouteParams({ group_source_app_name: 'Services' })
155
194
 
@@ -185,7 +224,7 @@ describe('FilterDropdownListHeaderComponent', () => {
185
224
 
186
225
  render(<FilterDropdownListHeaderComponent />)
187
226
 
188
- expect(menuItemNames()).toEqual(['All', 'DMs', 'More filters'])
227
+ expect(menuItemNames()).toEqual(['All', 'DMs', 'Unread', 'More filters'])
189
228
  })
190
229
 
191
230
  it('labels the archived list and carries it into More filters', () => {
@@ -234,6 +273,19 @@ describe('FilterDropdownListHeaderComponent', () => {
234
273
  expect(mockNavigate).toHaveBeenCalledWith('ConversationFilters', expect.any(Object))
235
274
  })
236
275
 
276
+ it('More filters carries the Unread filter into the sheet', () => {
277
+ setRouteParams({ show_unread: true })
278
+
279
+ render(<FilterDropdownListHeaderComponent />)
280
+
281
+ fireEvent.press(screen.getByRole('menuitem', { name: 'More filters' }))
282
+
283
+ expect(mockNavigate).toHaveBeenCalledWith(
284
+ 'ConversationFilters',
285
+ expect.objectContaining({ show_unread: true })
286
+ )
287
+ })
288
+
237
289
  it('shows the search button only when search is enabled', () => {
238
290
  mockSearchEnabled.mockReturnValue(false)
239
291
  const view = render(<FilterDropdownListHeaderComponent />)
@@ -33,6 +33,7 @@ enum FilterTypes {
33
33
  Groups = 'Groups',
34
34
  Teams = 'Teams',
35
35
  DMs = 'DMs',
36
+ Unread = 'Unread',
36
37
  More = 'More',
37
38
  }
38
39
 
@@ -41,6 +42,7 @@ const FILTER_PARAMS: Record<Exclude<FilterTypes, FilterTypes.More>, Conversation
41
42
  [FilterTypes.Groups]: { group_source_app_name: 'Groups' },
42
43
  [FilterTypes.Teams]: { group_source_app_name: 'Services' },
43
44
  [FilterTypes.DMs]: { show_direct_messages: true },
45
+ [FilterTypes.Unread]: { show_unread: true },
44
46
  }
45
47
 
46
48
  export const FilterDropdownListHeaderComponent = () => {
@@ -59,6 +61,7 @@ export const FilterDropdownListHeaderComponent = () => {
59
61
  group_source_app_name = '',
60
62
  show_direct_messages,
61
63
  show_archived,
64
+ show_unread,
62
65
  } = route.params || {}
63
66
  const currentPersonCache = useCurrentPersonCache()
64
67
  const { markAllRead, isPending } = useMarkAllRead()
@@ -81,10 +84,12 @@ export const FilterDropdownListHeaderComponent = () => {
81
84
  return FilterTypes.Teams
82
85
  } else if (show_direct_messages) {
83
86
  return FilterTypes.DMs
87
+ } else if (show_unread) {
88
+ return FilterTypes.Unread
84
89
  }
85
90
 
86
91
  return FilterTypes.All
87
- }, [chat_group_graph_id, group_source_app_name, show_direct_messages, show_archived])
92
+ }, [chat_group_graph_id, group_source_app_name, show_direct_messages, show_archived, show_unread])
88
93
 
89
94
  const options: FilterDropdownOption[] = useMemo(
90
95
  () => [
@@ -92,6 +97,7 @@ export const FilterDropdownListHeaderComponent = () => {
92
97
  ...(canFilterByGroups ? [{ id: FilterTypes.Groups, label: FilterTypes.Groups }] : []),
93
98
  ...(canFilterByTeams ? [{ id: FilterTypes.Teams, label: FilterTypes.Teams }] : []),
94
99
  ...(canFilterByDMs ? [{ id: FilterTypes.DMs, label: FilterTypes.DMs }] : []),
100
+ { id: FilterTypes.Unread, label: FilterTypes.Unread },
95
101
  ],
96
102
  [canFilterByGroups, canFilterByTeams, canFilterByDMs]
97
103
  )
@@ -142,6 +148,7 @@ export const FilterDropdownListHeaderComponent = () => {
142
148
  group_source_app_name: undefined,
143
149
  show_direct_messages: undefined,
144
150
  show_archived: undefined,
151
+ show_unread: undefined,
145
152
  ...params,
146
153
  })
147
154
  },
@@ -170,8 +177,16 @@ export const FilterDropdownListHeaderComponent = () => {
170
177
  group_source_app_name,
171
178
  show_direct_messages,
172
179
  show_archived,
180
+ show_unread,
173
181
  })
174
- }, [navigation, chat_group_graph_id, group_source_app_name, show_direct_messages, show_archived])
182
+ }, [
183
+ navigation,
184
+ chat_group_graph_id,
185
+ group_source_app_name,
186
+ show_direct_messages,
187
+ show_archived,
188
+ show_unread,
189
+ ])
175
190
 
176
191
  const handleMarkAllRead = useCallback(() => {
177
192
  currentPersonCache.update({ unreadCount: 0 })
@@ -19,6 +19,7 @@ export type ConversationsScreenProps = StaticScreenProps<{
19
19
  group_source_app_name?: AppName
20
20
  show_direct_messages?: boolean
21
21
  show_archived?: boolean
22
+ show_unread?: boolean
22
23
  }>
23
24
 
24
25
  export function ConversationsScreen({ route }: ConversationsScreenProps) {
@@ -13,6 +13,7 @@ describe('getConversationsRequestArgs', () => {
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
15
  ['show_archived', { show_archived: true }, 'mine_or_not_empty,archived'],
16
+ ['show_unread', { show_unread: true }, 'mine_or_not_empty,unread'],
16
17
  ])('%s uses filter: %s', (_label, args, expectedFilter) => {
17
18
  expect(getConversationsRequestArgs(args).data.filter).toBe(expectedFilter)
18
19
  })
@@ -7,6 +7,7 @@ export interface ConversationRequestArgs {
7
7
  group_source_app_name?: string
8
8
  show_direct_messages?: boolean
9
9
  show_archived?: boolean
10
+ show_unread?: boolean
10
11
  }
11
12
 
12
13
  export const getConversationsRequestArgs = ({
@@ -15,12 +16,15 @@ export const getConversationsRequestArgs = ({
15
16
  group_source_app_name,
16
17
  show_direct_messages,
17
18
  show_archived,
19
+ show_unread,
18
20
  }: Partial<ConversationRequestArgs> = {}): GetRequest => {
19
21
  let filter: string
20
22
  if (chat_group_graph_id) {
21
23
  filter = 'mine_or_not_empty,group'
22
24
  } else if (show_archived) {
23
25
  filter = 'mine_or_not_empty,archived'
26
+ } else if (show_unread) {
27
+ filter = 'mine_or_not_empty,unread'
24
28
  } else if (show_direct_messages) {
25
29
  filter = 'mine_or_not_empty,direct_message'
26
30
  } else if (group_source_app_name) {
@@ -6,6 +6,7 @@ export const MAX_FONT_SIZE_MULTIPLIER_LANDMARK = 1.2 // Use in headers, footers,
6
6
 
7
7
  export const CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL = 16 // TODO: move to `screens/conversation/utils/styles` when `/screens/conversation` is created
8
8
  export const MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH = 32 // TODO: move to `screens/conversation/utils/styles` when `/screens/conversation` is created
9
+ export const MESSAGE_BACKGROUND_OUTSET = 2
9
10
 
10
11
  export const platformFontWeightMedium = Platform.select({
11
12
  ios: tokens.fontWeightMedium,