@planningcenter/chat-react-native 4.4.0-rc.7 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/build/components/conversation/jump_to_bottom_button.d.ts.map +1 -1
  2. package/build/components/conversation/jump_to_bottom_button.js +42 -40
  3. package/build/components/conversation/jump_to_bottom_button.js.map +1 -1
  4. package/build/components/conversation/message.d.ts +2 -1
  5. package/build/components/conversation/message.d.ts.map +1 -1
  6. package/build/components/conversation/message.js +3 -1
  7. package/build/components/conversation/message.js.map +1 -1
  8. package/build/components/conversation/message_form.d.ts.map +1 -1
  9. package/build/components/conversation/message_form.js +3 -1
  10. package/build/components/conversation/message_form.js.map +1 -1
  11. package/build/components/conversation/message_list.d.ts.map +1 -1
  12. package/build/components/conversation/message_list.js +35 -31
  13. package/build/components/conversation/message_list.js.map +1 -1
  14. package/build/components/conversation/message_reaction.d.ts +2 -1
  15. package/build/components/conversation/message_reaction.d.ts.map +1 -1
  16. package/build/components/conversation/message_reaction.js +67 -60
  17. package/build/components/conversation/message_reaction.js.map +1 -1
  18. package/build/components/conversation/messages_disabled_banners.d.ts +7 -5
  19. package/build/components/conversation/messages_disabled_banners.d.ts.map +1 -1
  20. package/build/components/conversation/messages_disabled_banners.js +15 -14
  21. package/build/components/conversation/messages_disabled_banners.js.map +1 -1
  22. package/build/components/conversation/system_message.d.ts +2 -1
  23. package/build/components/conversation/system_message.d.ts.map +1 -1
  24. package/build/components/conversation/system_message.js +133 -100
  25. package/build/components/conversation/system_message.js.map +1 -1
  26. package/build/components/conversations/archived_results_footer.d.ts +6 -0
  27. package/build/components/conversations/archived_results_footer.d.ts.map +1 -0
  28. package/build/components/conversations/archived_results_footer.js +51 -0
  29. package/build/components/conversations/archived_results_footer.js.map +1 -0
  30. package/build/components/conversations/conversation_actions.d.ts.map +1 -1
  31. package/build/components/conversations/conversation_actions.js +241 -206
  32. package/build/components/conversations/conversation_actions.js.map +1 -1
  33. package/build/components/conversations/conversation_preview.d.ts.map +1 -1
  34. package/build/components/conversations/conversation_preview.js +102 -93
  35. package/build/components/conversations/conversation_preview.js.map +1 -1
  36. package/build/components/conversations/conversations.d.ts.map +1 -1
  37. package/build/components/conversations/conversations.js +66 -41
  38. package/build/components/conversations/conversations.js.map +1 -1
  39. package/build/components/conversations/conversations_blank_state.d.ts.map +1 -1
  40. package/build/components/conversations/conversations_blank_state.js +55 -12
  41. package/build/components/conversations/conversations_blank_state.js.map +1 -1
  42. package/build/components/conversations/filtered_results_footer.d.ts +2 -0
  43. package/build/components/conversations/filtered_results_footer.d.ts.map +1 -0
  44. package/build/components/conversations/filtered_results_footer.js +77 -0
  45. package/build/components/conversations/filtered_results_footer.js.map +1 -0
  46. package/build/components/display/action_button.d.ts.map +1 -1
  47. package/build/components/display/action_button.js +18 -16
  48. package/build/components/display/action_button.js.map +1 -1
  49. package/build/components/display/filter_dropdown.d.ts +14 -0
  50. package/build/components/display/filter_dropdown.d.ts.map +1 -0
  51. package/build/components/display/filter_dropdown.js +193 -0
  52. package/build/components/display/filter_dropdown.js.map +1 -0
  53. package/build/components/display/icon_tile.d.ts +13 -0
  54. package/build/components/display/icon_tile.d.ts.map +1 -0
  55. package/build/components/display/icon_tile.js +149 -0
  56. package/build/components/display/icon_tile.js.map +1 -0
  57. package/build/components/display/index.d.ts +2 -0
  58. package/build/components/display/index.d.ts.map +1 -1
  59. package/build/components/display/index.js +2 -0
  60. package/build/components/display/index.js.map +1 -1
  61. package/build/contexts/api_provider.d.ts.map +1 -1
  62. package/build/contexts/api_provider.js +21 -9
  63. package/build/contexts/api_provider.js.map +1 -1
  64. package/build/contexts/conversations_context.d.ts.map +1 -1
  65. package/build/contexts/conversations_context.js +21 -18
  66. package/build/contexts/conversations_context.js.map +1 -1
  67. package/build/hooks/groups/use_group_members_for_new_conversation.d.ts +196 -16
  68. package/build/hooks/groups/use_group_members_for_new_conversation.d.ts.map +1 -1
  69. package/build/hooks/groups/use_group_members_for_new_conversation.js +72 -58
  70. package/build/hooks/groups/use_group_members_for_new_conversation.js.map +1 -1
  71. package/build/hooks/index.d.ts +3 -0
  72. package/build/hooks/index.d.ts.map +1 -1
  73. package/build/hooks/index.js +3 -0
  74. package/build/hooks/index.js.map +1 -1
  75. package/build/hooks/use_animated_message_background_color.d.ts.map +1 -1
  76. package/build/hooks/use_animated_message_background_color.js +5 -3
  77. package/build/hooks/use_animated_message_background_color.js.map +1 -1
  78. package/build/hooks/use_api.d.ts +3 -2
  79. package/build/hooks/use_api.d.ts.map +1 -1
  80. package/build/hooks/use_api.js.map +1 -1
  81. package/build/hooks/use_can_browse_archived_conversations.d.ts +2 -0
  82. package/build/hooks/use_can_browse_archived_conversations.d.ts.map +1 -0
  83. package/build/hooks/use_can_browse_archived_conversations.js +29 -0
  84. package/build/hooks/use_can_browse_archived_conversations.js.map +1 -0
  85. package/build/hooks/use_clear_conversation_filters.d.ts +2 -0
  86. package/build/hooks/use_clear_conversation_filters.d.ts.map +1 -0
  87. package/build/hooks/use_clear_conversation_filters.js +25 -0
  88. package/build/hooks/use_clear_conversation_filters.js.map +1 -0
  89. package/build/hooks/use_conversation.d.ts +73 -1
  90. package/build/hooks/use_conversation.d.ts.map +1 -1
  91. package/build/hooks/use_conversation.js +130 -3
  92. package/build/hooks/use_conversation.js.map +1 -1
  93. package/build/hooks/use_conversation_messages.d.ts.map +1 -1
  94. package/build/hooks/use_conversation_messages.js +29 -26
  95. package/build/hooks/use_conversation_messages.js.map +1 -1
  96. package/build/hooks/use_current_person.d.ts.map +1 -1
  97. package/build/hooks/use_current_person.js +1 -1
  98. package/build/hooks/use_current_person.js.map +1 -1
  99. package/build/hooks/use_jolt.d.ts +1 -0
  100. package/build/hooks/use_jolt.d.ts.map +1 -1
  101. package/build/hooks/use_jolt.js +59 -0
  102. package/build/hooks/use_jolt.js.map +1 -1
  103. package/build/hooks/use_manual_archiving_enabled.d.ts +2 -0
  104. package/build/hooks/use_manual_archiving_enabled.d.ts.map +1 -0
  105. package/build/hooks/use_manual_archiving_enabled.js +18 -0
  106. package/build/hooks/use_manual_archiving_enabled.js.map +1 -0
  107. package/build/hooks/use_mark_latest_message_read.d.ts.map +1 -1
  108. package/build/hooks/use_mark_latest_message_read.js +3 -3
  109. package/build/hooks/use_mark_latest_message_read.js.map +1 -1
  110. package/build/hooks/use_reduce_motion.d.ts +2 -0
  111. package/build/hooks/use_reduce_motion.d.ts.map +1 -0
  112. package/build/hooks/use_reduce_motion.js +28 -0
  113. package/build/hooks/use_reduce_motion.js.map +1 -0
  114. package/build/hooks/use_unarchive_conversation_action.d.ts +7 -0
  115. package/build/hooks/use_unarchive_conversation_action.d.ts.map +1 -0
  116. package/build/hooks/use_unarchive_conversation_action.js +52 -0
  117. package/build/hooks/use_unarchive_conversation_action.js.map +1 -0
  118. package/build/screens/attachment_actions/attachment_actions_screen.d.ts.map +1 -1
  119. package/build/screens/attachment_actions/attachment_actions_screen.js +84 -61
  120. package/build/screens/attachment_actions/attachment_actions_screen.js.map +1 -1
  121. package/build/screens/conversation_details_screen.d.ts.map +1 -1
  122. package/build/screens/conversation_details_screen.js +490 -395
  123. package/build/screens/conversation_details_screen.js.map +1 -1
  124. package/build/screens/conversation_filters/components/conversation_filters.d.ts.map +1 -1
  125. package/build/screens/conversation_filters/components/conversation_filters.js +88 -18
  126. package/build/screens/conversation_filters/components/conversation_filters.js.map +1 -1
  127. package/build/screens/conversation_filters/components/rows.d.ts +9 -0
  128. package/build/screens/conversation_filters/components/rows.d.ts.map +1 -1
  129. package/build/screens/conversation_filters/components/rows.js +93 -1
  130. package/build/screens/conversation_filters/components/rows.js.map +1 -1
  131. package/build/screens/conversation_filters/context/conversation_filter_context.d.ts.map +1 -1
  132. package/build/screens/conversation_filters/context/conversation_filter_context.js +8 -4
  133. package/build/screens/conversation_filters/context/conversation_filter_context.js.map +1 -1
  134. package/build/screens/conversation_filters/filter_types.d.ts +1 -0
  135. package/build/screens/conversation_filters/filter_types.d.ts.map +1 -1
  136. package/build/screens/conversation_filters/filter_types.js +1 -0
  137. package/build/screens/conversation_filters/filter_types.js.map +1 -1
  138. package/build/screens/conversation_filters/screen_props.d.ts +1 -0
  139. package/build/screens/conversation_filters/screen_props.d.ts.map +1 -1
  140. package/build/screens/conversation_filters/screen_props.js.map +1 -1
  141. package/build/screens/conversation_filters_screen.d.ts +1 -0
  142. package/build/screens/conversation_filters_screen.d.ts.map +1 -1
  143. package/build/screens/conversation_filters_screen.js +79 -48
  144. package/build/screens/conversation_filters_screen.js.map +1 -1
  145. package/build/screens/conversation_new/components/groups_form.d.ts.map +1 -1
  146. package/build/screens/conversation_new/components/groups_form.js +37 -34
  147. package/build/screens/conversation_new/components/groups_form.js.map +1 -1
  148. package/build/screens/conversation_screen.d.ts.map +1 -1
  149. package/build/screens/conversation_screen.js +13 -10
  150. package/build/screens/conversation_screen.js.map +1 -1
  151. package/build/screens/conversations/components/filter_dropdown_list_header_component.d.ts +2 -0
  152. package/build/screens/conversations/components/filter_dropdown_list_header_component.d.ts.map +1 -0
  153. package/build/screens/conversations/components/filter_dropdown_list_header_component.js +580 -0
  154. package/build/screens/conversations/components/filter_dropdown_list_header_component.js.map +1 -0
  155. package/build/screens/conversations/components/list_header_component.d.ts.map +1 -1
  156. package/build/screens/conversations/components/list_header_component.js +95 -90
  157. package/build/screens/conversations/components/list_header_component.js.map +1 -1
  158. package/build/screens/conversations/components/search_list_header_component.d.ts.map +1 -1
  159. package/build/screens/conversations/components/search_list_header_component.js +100 -95
  160. package/build/screens/conversations/components/search_list_header_component.js.map +1 -1
  161. package/build/screens/conversations/conversations_screen.d.ts +1 -0
  162. package/build/screens/conversations/conversations_screen.d.ts.map +1 -1
  163. package/build/screens/conversations/conversations_screen.js +15 -2
  164. package/build/screens/conversations/conversations_screen.js.map +1 -1
  165. package/build/screens/design_system_screen.d.ts.map +1 -1
  166. package/build/screens/design_system_screen.js +275 -215
  167. package/build/screens/design_system_screen.js.map +1 -1
  168. package/build/screens/message_actions_screen.d.ts.map +1 -1
  169. package/build/screens/message_actions_screen.js +112 -92
  170. package/build/screens/message_actions_screen.js.map +1 -1
  171. package/build/screens/search/search_screen.d.ts.map +1 -1
  172. package/build/screens/search/search_screen.js +10 -2
  173. package/build/screens/search/search_screen.js.map +1 -1
  174. package/build/screens/send_giphy_screen.d.ts +1 -0
  175. package/build/screens/send_giphy_screen.d.ts.map +1 -1
  176. package/build/screens/send_giphy_screen.js +108 -106
  177. package/build/screens/send_giphy_screen.js.map +1 -1
  178. package/build/screens/team_conversation_screen.js +1 -1
  179. package/build/screens/team_conversation_screen.js.map +1 -1
  180. package/build/types/jolt_events/message_events.d.ts +2 -0
  181. package/build/types/jolt_events/message_events.d.ts.map +1 -1
  182. package/build/types/jolt_events/message_events.js.map +1 -1
  183. package/build/types/resources/conversation.d.ts +2 -2
  184. package/build/types/resources/conversation.d.ts.map +1 -1
  185. package/build/types/resources/conversation.js.map +1 -1
  186. package/build/types/resources/member_ability.d.ts +2 -0
  187. package/build/types/resources/member_ability.d.ts.map +1 -1
  188. package/build/types/resources/member_ability.js.map +1 -1
  189. package/build/types/resources/message.d.ts +2 -0
  190. package/build/types/resources/message.d.ts.map +1 -1
  191. package/build/types/resources/message.js.map +1 -1
  192. package/build/types/resources/person.d.ts +1 -0
  193. package/build/types/resources/person.d.ts.map +1 -1
  194. package/build/types/resources/person.js.map +1 -1
  195. package/build/utils/archived_conversation.d.ts +5 -0
  196. package/build/utils/archived_conversation.d.ts.map +1 -0
  197. package/build/utils/archived_conversation.js +5 -0
  198. package/build/utils/archived_conversation.js.map +1 -0
  199. package/build/utils/date.d.ts +1 -0
  200. package/build/utils/date.d.ts.map +1 -1
  201. package/build/utils/date.js +9 -0
  202. package/build/utils/date.js.map +1 -1
  203. package/build/utils/index.d.ts +1 -0
  204. package/build/utils/index.d.ts.map +1 -1
  205. package/build/utils/index.js +1 -0
  206. package/build/utils/index.js.map +1 -1
  207. package/build/utils/jolt/transform_message_event_data_to_message_resource.d.ts.map +1 -1
  208. package/build/utils/jolt/transform_message_event_data_to_message_resource.js +2 -1
  209. package/build/utils/jolt/transform_message_event_data_to_message_resource.js.map +1 -1
  210. package/build/utils/native_adapters/configuration.d.ts +3 -0
  211. package/build/utils/native_adapters/configuration.d.ts.map +1 -1
  212. package/build/utils/native_adapters/configuration.js +3 -0
  213. package/build/utils/native_adapters/configuration.js.map +1 -1
  214. package/build/utils/native_adapters/index.d.ts +1 -0
  215. package/build/utils/native_adapters/index.d.ts.map +1 -1
  216. package/build/utils/native_adapters/index.js +1 -0
  217. package/build/utils/native_adapters/index.js.map +1 -1
  218. package/build/utils/native_adapters/menu.d.ts +41 -0
  219. package/build/utils/native_adapters/menu.d.ts.map +1 -0
  220. package/build/utils/native_adapters/menu.js +56 -0
  221. package/build/utils/native_adapters/menu.js.map +1 -0
  222. package/build/utils/request/conversation.d.ts +2 -1
  223. package/build/utils/request/conversation.d.ts.map +1 -1
  224. package/build/utils/request/conversation.js +4 -1
  225. package/build/utils/request/conversation.js.map +1 -1
  226. package/build/utils/request/messages_data_options.d.ts.map +1 -1
  227. package/build/utils/request/messages_data_options.js +1 -1
  228. package/build/utils/request/messages_data_options.js.map +1 -1
  229. package/build/utils/system_messages.d.ts +7 -5
  230. package/build/utils/system_messages.d.ts.map +1 -1
  231. package/build/utils/system_messages.js +10 -2
  232. package/build/utils/system_messages.js.map +1 -1
  233. package/build/vendor/font_awesome_pro_solid_icons.d.ts +1 -0
  234. package/build/vendor/font_awesome_pro_solid_icons.d.ts.map +1 -1
  235. package/build/vendor/font_awesome_pro_solid_icons.js +5 -0
  236. package/build/vendor/font_awesome_pro_solid_icons.js.map +1 -1
  237. package/package.json +3 -3
  238. package/src/__tests__/hooks/use_can_browse_archived_conversations.test.tsx +98 -0
  239. package/src/__tests__/screens/conversation_filters/components/conversation_filters.test.tsx +132 -0
  240. package/src/__tests__/screens/conversation_filters_screen.test.tsx +76 -0
  241. package/src/__tests__/screens/conversation_new/components/services_form.test.tsx +1 -0
  242. package/src/__tests__/screens/message_actions_screen.test.tsx +65 -2
  243. package/src/__tests__/screens/send_giphy_screen.test.tsx +102 -0
  244. package/src/__tests__/utils/system_messages.ts +19 -32
  245. package/src/components/conversation/__tests__/message_list.test.tsx +68 -2
  246. package/src/components/conversation/__tests__/system_message.test.tsx +106 -0
  247. package/src/components/conversation/jump_to_bottom_button.tsx +4 -3
  248. package/src/components/conversation/message.tsx +3 -0
  249. package/src/components/conversation/message_form.tsx +3 -1
  250. package/src/components/conversation/message_list.tsx +9 -1
  251. package/src/components/conversation/message_reaction.tsx +7 -2
  252. package/src/components/conversation/messages_disabled_banners.tsx +17 -18
  253. package/src/components/conversation/system_message.tsx +44 -16
  254. package/src/components/conversations/__tests__/conversation_actions.test.tsx +67 -0
  255. package/src/components/conversations/__tests__/conversation_preview.test.tsx +105 -0
  256. package/src/components/conversations/__tests__/conversations.test.tsx +93 -0
  257. package/src/components/conversations/__tests__/conversations_blank_state.test.tsx +73 -0
  258. package/src/components/conversations/archived_results_footer.tsx +36 -0
  259. package/src/components/conversations/conversation_actions.tsx +27 -19
  260. package/src/components/conversations/conversation_preview.tsx +39 -10
  261. package/src/components/conversations/conversations.tsx +26 -3
  262. package/src/components/conversations/conversations_blank_state.tsx +21 -0
  263. package/src/components/conversations/filtered_results_footer.tsx +38 -0
  264. package/src/components/display/__tests__/filter_dropdown.test.tsx +114 -0
  265. package/src/components/display/__tests__/icon_tile.test.tsx +16 -0
  266. package/src/components/display/action_button.tsx +4 -3
  267. package/src/components/display/filter_dropdown.tsx +124 -0
  268. package/src/components/display/icon_tile.tsx +67 -0
  269. package/src/components/display/index.ts +2 -0
  270. package/src/contexts/api_provider.tsx +8 -0
  271. package/src/contexts/conversations_context.tsx +2 -1
  272. package/src/hooks/__tests__/use_conversation_archive.test.tsx +152 -0
  273. package/src/hooks/__tests__/use_conversation_jolt_events.test.tsx +74 -0
  274. package/src/hooks/__tests__/use_conversation_messages.test.tsx +10 -2
  275. package/src/hooks/__tests__/use_jolt.test.tsx +100 -1
  276. package/src/hooks/__tests__/use_mark_latest_message_read.test.tsx +21 -7
  277. package/src/hooks/__tests__/use_reduce_motion.test.tsx +61 -0
  278. package/src/hooks/__tests__/use_unarchive_conversation_action.test.tsx +67 -0
  279. package/src/hooks/groups/use_group_members_for_new_conversation.ts +21 -16
  280. package/src/hooks/index.ts +3 -0
  281. package/src/hooks/use_animated_message_background_color.ts +6 -3
  282. package/src/hooks/use_api.ts +9 -2
  283. package/src/hooks/use_can_browse_archived_conversations.ts +17 -0
  284. package/src/hooks/use_clear_conversation_filters.ts +16 -0
  285. package/src/hooks/use_conversation.ts +44 -3
  286. package/src/hooks/use_conversation_messages.ts +7 -5
  287. package/src/hooks/use_current_person.ts +1 -0
  288. package/src/hooks/use_jolt.ts +57 -1
  289. package/src/hooks/use_manual_archiving_enabled.ts +10 -0
  290. package/src/hooks/use_mark_latest_message_read.ts +4 -3
  291. package/src/hooks/use_reduce_motion.ts +19 -0
  292. package/src/hooks/use_unarchive_conversation_action.ts +24 -0
  293. package/src/screens/__tests__/conversation_details_screen.test.tsx +305 -0
  294. package/src/screens/attachment_actions/__tests__/attachment_actions_screen.test.tsx +72 -0
  295. package/src/screens/attachment_actions/attachment_actions_screen.tsx +3 -1
  296. package/src/screens/conversation_details_screen.tsx +80 -16
  297. package/src/screens/conversation_filters/__tests__/conversation_filters.test.tsx +101 -0
  298. package/src/screens/conversation_filters/components/conversation_filters.tsx +40 -7
  299. package/src/screens/conversation_filters/components/rows.tsx +29 -1
  300. package/src/screens/conversation_filters/context/conversation_filter_context.tsx +4 -0
  301. package/src/screens/conversation_filters/filter_types.ts +1 -0
  302. package/src/screens/conversation_filters/screen_props.ts +1 -0
  303. package/src/screens/conversation_filters_screen.tsx +25 -5
  304. package/src/screens/conversation_new/components/groups_form.tsx +1 -0
  305. package/src/screens/conversation_screen.tsx +4 -2
  306. package/src/screens/conversations/__tests__/conversations_screen.test.tsx +52 -0
  307. package/src/screens/conversations/components/__tests__/filter_dropdown_list_header_component.test.tsx +259 -0
  308. package/src/screens/conversations/components/__tests__/list_header_component.test.tsx +21 -4
  309. package/src/screens/conversations/components/filter_dropdown_list_header_component.tsx +286 -0
  310. package/src/screens/conversations/components/list_header_component.tsx +17 -12
  311. package/src/screens/conversations/components/search_list_header_component.tsx +17 -12
  312. package/src/screens/conversations/conversations_screen.tsx +15 -2
  313. package/src/screens/design_system_screen.tsx +26 -0
  314. package/src/screens/message_actions_screen.tsx +29 -20
  315. package/src/screens/search/__tests__/search_screen.test.tsx +13 -1
  316. package/src/screens/search/search_screen.tsx +8 -2
  317. package/src/screens/send_giphy_screen.tsx +6 -3
  318. package/src/screens/team_conversation_screen.tsx +1 -1
  319. package/src/types/jolt_events/message_events.ts +2 -0
  320. package/src/types/resources/conversation.ts +2 -2
  321. package/src/types/resources/member_ability.ts +2 -0
  322. package/src/types/resources/message.ts +3 -0
  323. package/src/types/resources/person.ts +1 -0
  324. package/src/utils/__tests__/conversation.test.ts +1 -0
  325. package/src/utils/__tests__/date.test.ts +23 -0
  326. package/src/utils/__tests__/group_messages.test.ts +1 -0
  327. package/src/utils/archived_conversation.ts +13 -0
  328. package/src/utils/date.ts +8 -0
  329. package/src/utils/index.ts +1 -0
  330. package/src/utils/jolt/__tests__/transform_message_event_data_to_message_resource.test.ts +35 -0
  331. package/src/utils/jolt/transform_message_event_data_to_message_resource.ts +1 -0
  332. package/src/utils/native_adapters/__tests__/menu.test.tsx +46 -0
  333. package/src/utils/native_adapters/configuration.ts +5 -0
  334. package/src/utils/native_adapters/index.ts +1 -0
  335. package/src/utils/native_adapters/menu.tsx +70 -0
  336. package/src/utils/request/conversation.ts +4 -0
  337. package/src/utils/request/messages_data_options.ts +1 -0
  338. package/src/utils/system_messages.ts +19 -10
  339. package/src/vendor/font_awesome_pro_solid_icons.ts +12 -0
@@ -4,10 +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'
12
+ import { useFilterDropdownEnabled } from '../../../hooks/use_filter_dropdown_enabled'
13
+ import { faBoxArchive } from '../../../vendor/font_awesome_pro_solid_icons'
11
14
  import { FilterContext, useFilterContext } from '../context/conversation_filter_context'
12
15
  import { FilterTypes } from '../filter_types'
13
16
  import { useGroupsToFilter, useTeamsToFilter } from '../hooks/filters'
@@ -16,6 +19,8 @@ import {
16
19
  FilterRow,
17
20
  GroupRow,
18
21
  GroupRowProps,
22
+ OtherRow,
23
+ OtherRowProps,
19
24
  TeamRow,
20
25
  TeamRowProps,
21
26
  ViewMore,
@@ -32,6 +37,7 @@ enum SectionTypes {
32
37
  header,
33
38
  hidden,
34
39
  more,
40
+ other,
35
41
  teams,
36
42
  }
37
43
 
@@ -41,6 +47,7 @@ type SectionListData = Array<
41
47
  | DataItem<HeaderProps, SectionTypes.header>
42
48
  | DataItem<any, SectionTypes.hidden>
43
49
  | DataItem<ViewMoreRowProps, SectionTypes.more>
50
+ | DataItem<OtherRowProps, SectionTypes.other>
44
51
  | DataItem<TeamRowProps, SectionTypes.teams>
45
52
  >
46
53
 
@@ -57,11 +64,18 @@ interface DataItem<T, TName extends SectionTypes> {
57
64
  export const ConversationFilters = () => {
58
65
  const styles = useStyles()
59
66
  const { setScrollOffset, params } = useContext(FilterContext)
60
- const { chat_group_graph_id, group_source_app_name = '', show_direct_messages } = params
67
+ const {
68
+ chat_group_graph_id,
69
+ group_source_app_name = '',
70
+ show_direct_messages,
71
+ show_archived,
72
+ } = params
61
73
 
74
+ const filterDropdownEnabled = useFilterDropdownEnabled()
62
75
  const directMessagesEligible = useDirectMessagesEligible()
63
76
  const hasDirectMessages = useHasDirectMessages()
64
77
  const canFilterByDMs = directMessagesEligible && hasDirectMessages
78
+ const canFilterByArchived = useCanBrowseArchivedConversations()
65
79
 
66
80
  const activeFilter: FilterTypes = useMemo(() => {
67
81
  if (chat_group_graph_id) {
@@ -72,10 +86,12 @@ export const ConversationFilters = () => {
72
86
  return FilterTypes.Teams
73
87
  } else if (show_direct_messages) {
74
88
  return FilterTypes.DirectMessages
89
+ } else if (show_archived) {
90
+ return FilterTypes.Archived
75
91
  }
76
92
 
77
93
  return FilterTypes.All
78
- }, [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])
79
95
 
80
96
  const { groups = [] } = useGroupsToFilter()
81
97
  const { teams = [] } = useTeamsToFilter()
@@ -101,8 +117,9 @@ export const ConversationFilters = () => {
101
117
  const groupItemData = selectActiveWithFirst(5)(groupItems)
102
118
  const teamItemData = selectActiveWithFirst(5)(teamItems)
103
119
 
104
- const hideAppFilters = groupItems.length < 1 || teamItems.length < 1
105
- const showGeneralFilters = !hideAppFilters || canFilterByDMs
120
+ const showAppFilters = !filterDropdownEnabled && groupItems.length > 0 && teamItems.length > 0
121
+ const showDMFilter = !filterDropdownEnabled && canFilterByDMs
122
+ const showGeneralFilters = showAppFilters || showDMFilter
106
123
 
107
124
  const listData: SectionListData = [
108
125
  {
@@ -114,7 +131,7 @@ export const ConversationFilters = () => {
114
131
  data: { filter: FilterTypes.All, isActive: activeFilter === FilterTypes.All },
115
132
  },
116
133
  {
117
- type: hideAppFilters ? SectionTypes.hidden : SectionTypes.filter,
134
+ type: showAppFilters ? SectionTypes.filter : SectionTypes.hidden,
118
135
  data: {
119
136
  filter: FilterTypes.Groups,
120
137
  group_source_app_name: 'groups',
@@ -122,7 +139,7 @@ export const ConversationFilters = () => {
122
139
  },
123
140
  },
124
141
  {
125
- type: hideAppFilters ? SectionTypes.hidden : SectionTypes.filter,
142
+ type: showAppFilters ? SectionTypes.filter : SectionTypes.hidden,
126
143
  data: {
127
144
  filter: FilterTypes.Teams,
128
145
  group_source_app_name: 'services',
@@ -130,7 +147,7 @@ export const ConversationFilters = () => {
130
147
  },
131
148
  },
132
149
  {
133
- type: canFilterByDMs ? SectionTypes.filter : SectionTypes.hidden,
150
+ type: showDMFilter ? SectionTypes.filter : SectionTypes.hidden,
134
151
  data: {
135
152
  filter: FilterTypes.DirectMessages,
136
153
  show_direct_messages: true,
@@ -155,6 +172,20 @@ export const ConversationFilters = () => {
155
172
  type: teamItems.length > 5 ? SectionTypes.more : SectionTypes.hidden,
156
173
  data: { routeName: 'TeamFilters' },
157
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
+ },
158
189
  ]
159
190
 
160
191
  return (
@@ -175,6 +206,8 @@ export const ConversationFilters = () => {
175
206
  return <TeamRow {...item.data} />
176
207
  case SectionTypes.more:
177
208
  return <ViewMore {...item.data} />
209
+ case SectionTypes.other:
210
+ return <OtherRow {...item.data} />
178
211
  default:
179
212
  return null
180
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} shape="square" 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) => {
@@ -3,5 +3,6 @@ export enum FilterTypes {
3
3
  DirectMessages = 'Direct messages',
4
4
  Groups = 'All my groups',
5
5
  Teams = 'All my teams and plans',
6
+ Archived = 'Archived conversations',
6
7
  More = 'More',
7
8
  }
@@ -5,6 +5,7 @@ export type ConversationFiltersParams = {
5
5
  chat_group_graph_id?: GraphId
6
6
  group_source_app_name?: string
7
7
  show_direct_messages?: boolean
8
+ show_archived?: boolean
8
9
  }
9
10
 
10
11
  export type ConversationFiltersScreenProps = StaticScreenProps<ConversationFiltersParams>
@@ -1,11 +1,13 @@
1
- import { useHeaderHeight } from '@react-navigation/elements'
1
+ import { HeaderBackButton, useHeaderHeight } from '@react-navigation/elements'
2
2
  import * as ReactNavigationNative from '@react-navigation/native'
3
- import { RouteProp, useRoute } from '@react-navigation/native'
3
+ import { RouteProp, useNavigation, useRoute } from '@react-navigation/native'
4
4
  import { createNativeStackNavigator } from '@react-navigation/native-stack'
5
5
  import React, { useCallback } from 'react'
6
6
  import { LayoutChangeEvent, Platform, StyleSheet, useWindowDimensions, View } from 'react-native'
7
7
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
8
8
  import FormSheet, { getFormSheetScreenOptions } from '../components/primitive/form_sheet'
9
+ import { useTheme } from '../hooks'
10
+ import { useFilterDropdownEnabled } from '../hooks/use_filter_dropdown_enabled'
9
11
  import { ConversationFilters } from './conversation_filters/components/conversation_filters'
10
12
  import {
11
13
  FilterProvider,
@@ -86,16 +88,19 @@ export const ConversationFiltersScreen = (_props: ConversationFiltersScreenProps
86
88
  return (
87
89
  <FilterProvider>
88
90
  <FormSheet.Root contentStyle={styles.contentContainer} showAndroidSheetGrabber={false}>
89
- <FormSheetHeaderWithContext />
91
+ <ConversationFiltersHeader />
90
92
  <Filters />
91
93
  </FormSheet.Root>
92
94
  </FilterProvider>
93
95
  )
94
96
  }
95
97
 
96
- const FormSheetHeaderWithContext = () => {
98
+ export const ConversationFiltersHeader = () => {
97
99
  const { resetFilter, applyFilters, setFormSheetHeaderHeight } = useFilterContext()
100
+ const navigation = useNavigation()
98
101
  const route = useRoute<RouteProp<ConversationFiltersScreenProps['route']>>()
102
+ const filterDropdownEnabled = useFilterDropdownEnabled()
103
+ const { colors } = useTheme()
99
104
  const styles = useStyles()
100
105
 
101
106
  const handleFormSheetHeaderLayout = useCallback(
@@ -108,7 +113,18 @@ const FormSheetHeaderWithContext = () => {
108
113
  return (
109
114
  <View onLayout={handleFormSheetHeaderLayout}>
110
115
  <FormSheet.Header style={styles.header}>
111
- <FormSheet.HeaderTitle>Filters</FormSheet.HeaderTitle>
116
+ {Platform.OS === 'android' && (
117
+ <HeaderBackButton
118
+ displayMode="minimal"
119
+ accessibilityLabel="Back"
120
+ tintColor={colors.iconColorDefaultPrimary}
121
+ onPress={navigation.goBack}
122
+ style={styles.backButton}
123
+ />
124
+ )}
125
+ <FormSheet.HeaderTitle>
126
+ {filterDropdownEnabled ? 'More filters' : 'Filters'}
127
+ </FormSheet.HeaderTitle>
112
128
  <FormSheet.HeaderActions>
113
129
  <FormSheet.HeaderTextButton onPress={resetFilter}>Reset</FormSheet.HeaderTextButton>
114
130
  <FormSheet.HeaderButton title="Apply" onPress={() => applyFilters(route.params)} />
@@ -141,5 +157,9 @@ const useStyles = () => {
141
157
  // separator the way the non-scrolling reaction sheet does.
142
158
  borderBottomWidth: 0,
143
159
  },
160
+ backButton: {
161
+ marginLeft: -3,
162
+ marginRight: 8,
163
+ },
144
164
  })
145
165
  }
@@ -125,6 +125,7 @@ export const GroupsForm = ({ groupId, chat_group_graph_id, avatarSelection }: Gr
125
125
  memberData={groupMemberships.adultMembers}
126
126
  loadingMore={groupMemberships.isFetchingNextPage}
127
127
  onEndReached={groupMemberships.fetchNextPage}
128
+ listEmptyText={groupMemberships.isLoading ? 'Loading members...' : undefined}
128
129
  FormContent={
129
130
  <FormContent
130
131
  group={group}
@@ -40,6 +40,7 @@ import {
40
40
  usePublishProductAnalyticsEvent,
41
41
  } from '../hooks/use_product_analytics'
42
42
  import { useRestAwayFromDismissEdge } from '../hooks/use_rest_away_from_dismiss_edge'
43
+ import { useUnarchiveConversationAction } from '../hooks/use_unarchive_conversation_action'
43
44
  import { useVideoModerationJoltEvents } from '../hooks/use_video_moderation_jolt_events'
44
45
  import { ConversationResource } from '../types/resources/conversation'
45
46
  import { ConversationBadgeResource } from '../types/resources/conversation_badge'
@@ -158,7 +159,7 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
158
159
  })
159
160
  } else {
160
161
  navigation.setParams({
161
- title: title,
162
+ title: title ?? undefined,
162
163
  badge: badges?.[0],
163
164
  deleted: conversation?.deleted,
164
165
  muted,
@@ -265,12 +266,13 @@ function ConversationBottomBar({
265
266
  onBeforeSubmit,
266
267
  }: ConversationBottomBarProps) {
267
268
  const messageLink = useMessageAvailableMembersAction(conversation)
269
+ const unarchiveAction = useUnarchiveConversationAction(conversation)
268
270
 
269
271
  if (conversation.disabled)
270
272
  return (
271
273
  <ConversationDisabledBanner
272
274
  message={conversation.disabledMessage ?? null}
273
- messageLink={messageLink}
275
+ action={unarchiveAction ?? messageLink}
274
276
  />
275
277
  )
276
278
  if (!canReply) return <MemberMessagesDisabledBanner />
@@ -2,6 +2,14 @@ import { QueryClientProvider } from '@tanstack/react-query'
2
2
  import { render } from '@testing-library/react-native'
3
3
  import React, { Suspense } from 'react'
4
4
  import { buildTestQueryClient } from '../../../__utils__/query_client'
5
+ import {
6
+ AudioAdapter,
7
+ ChatAdapters,
8
+ ClipboardAdapter,
9
+ ImagePickerAdapter,
10
+ MenuAdapter,
11
+ VideoAdapter,
12
+ } from '../../../utils/native_adapters'
5
13
  import { ConversationsScreen } from '../conversations_screen'
6
14
 
7
15
  const mockGet = jest.fn()
@@ -28,6 +36,26 @@ jest.mock('../components/search_list_header_component', () => {
28
36
  const { Text } = require('react-native')
29
37
  return { SearchListHeaderComponent: () => <Text>Search header</Text> }
30
38
  })
39
+ jest.mock('../components/filter_dropdown_list_header_component', () => {
40
+ const { Text } = require('react-native')
41
+ return { FilterDropdownListHeaderComponent: () => <Text>Filter dropdown header</Text> }
42
+ })
43
+
44
+ const configureAdapters = ({ menu }: { menu: boolean }) => {
45
+ ChatAdapters.configure({
46
+ clipboard: new ClipboardAdapter({
47
+ getStringAsync: async () => '',
48
+ setStringAsync: async () => {},
49
+ }),
50
+ audio: new AudioAdapter({ useAudio: () => ({}) as any }),
51
+ video: new VideoAdapter({ Player: (() => null) as any }),
52
+ imagePicker: new ImagePickerAdapter({
53
+ openCameraAsync: async () => ({ canceled: true, assets: null }),
54
+ openImageLibraryAsync: async () => ({ canceled: true, assets: null }),
55
+ }),
56
+ ...(menu ? { menu: new MenuAdapter({ View: () => null }) } : {}),
57
+ })
58
+ }
31
59
 
32
60
  const renderWithFeatures = (enabledFeatures: string[]) => {
33
61
  mockGet.mockResolvedValue({
@@ -51,6 +79,10 @@ const renderWithFeatures = (enabledFeatures: string[]) => {
51
79
  }
52
80
 
53
81
  describe('ConversationsScreen header gating', () => {
82
+ beforeEach(() => {
83
+ configureAdapters({ menu: true })
84
+ })
85
+
54
86
  it('shows the search header with only the QA flag on', async () => {
55
87
  const view = renderWithFeatures(['QA_MOBILE_search'])
56
88
 
@@ -63,6 +95,26 @@ describe('ConversationsScreen header gating', () => {
63
95
  expect(await view.findByText('Search header')).toBeTruthy()
64
96
  })
65
97
 
98
+ it('shows the filter dropdown header over the search header when its flag is on', async () => {
99
+ const view = renderWithFeatures(['QA_MOBILE_filter_dropdown', 'ROLLOUT_MOBILE_search'])
100
+
101
+ expect(await view.findByText('Filter dropdown header')).toBeTruthy()
102
+ })
103
+
104
+ it('shows the filter dropdown header with only its rollout flag on', async () => {
105
+ const view = renderWithFeatures(['ROLLOUT_MOBILE_filter_dropdown'])
106
+
107
+ expect(await view.findByText('Filter dropdown header')).toBeTruthy()
108
+ })
109
+
110
+ it('falls back to the search header when the host has no menu adapter', async () => {
111
+ configureAdapters({ menu: false })
112
+
113
+ const view = renderWithFeatures(['QA_MOBILE_filter_dropdown', 'ROLLOUT_MOBILE_search'])
114
+
115
+ expect(await view.findByText('Search header')).toBeTruthy()
116
+ })
117
+
66
118
  it('shows the default header with neither flag on', async () => {
67
119
  const view = renderWithFeatures(['ROLLOUT_WEB_search'])
68
120
 
@@ -0,0 +1,259 @@
1
+ import { fireEvent, render, screen } from '@testing-library/react-native'
2
+ import React from 'react'
3
+ import { Pressable, Text } from 'react-native'
4
+ import {
5
+ AudioAdapter,
6
+ ChatAdapters,
7
+ ClipboardAdapter,
8
+ ImagePickerAdapter,
9
+ MenuAction,
10
+ MenuAdapter,
11
+ MenuViewProps,
12
+ VideoAdapter,
13
+ } from '../../../../utils/native_adapters'
14
+ import { FilterDropdownListHeaderComponent } from '../filter_dropdown_list_header_component'
15
+
16
+ const mockNavigate = jest.fn()
17
+ const mockSetParams = jest.fn()
18
+ const mockRouteParams: Record<string, unknown> = {}
19
+ const mockCanDisplayGroups = jest.fn()
20
+ const mockSearchEnabled = jest.fn()
21
+ const mockCanBrowseArchived = jest.fn()
22
+ const mockGroupsByGraphId: Record<string, { name: string }> = {}
23
+
24
+ jest.mock('@react-navigation/native', () => ({
25
+ ...jest.requireActual('@react-navigation/native'),
26
+ useNavigation: () => ({ setParams: mockSetParams, navigate: mockNavigate }),
27
+ useRoute: () => ({ params: mockRouteParams }),
28
+ }))
29
+
30
+ jest.mock('../../../../hooks/use_api', () => ({
31
+ useApiGet: ({ url }: { url: string }, { enabled }: { enabled: boolean }) => ({
32
+ data: enabled ? mockGroupsByGraphId[url.replace('/me/groups/', '')] : undefined,
33
+ }),
34
+ }))
35
+ jest.mock('../../../../hooks/use_groups', () => ({
36
+ useCanDisplayGroups: (args: { source_type: string }) => mockCanDisplayGroups(args),
37
+ }))
38
+ jest.mock('../../../../hooks/use_direct_messages_eligible', () => ({
39
+ useDirectMessagesEligible: () => true,
40
+ }))
41
+ jest.mock('../../../../hooks/use_has_direct_messages', () => ({
42
+ useHasDirectMessages: () => true,
43
+ }))
44
+ jest.mock('../../../../hooks/use_can_browse_archived_conversations', () => ({
45
+ useCanBrowseArchivedConversations: () => mockCanBrowseArchived(),
46
+ }))
47
+ jest.mock('../../../../hooks/use_chat_permissions', () => ({
48
+ useCanCreateConversations: () => true,
49
+ }))
50
+ jest.mock('../../../../hooks/use_new_conversation_entry', () => ({
51
+ useNewConversationEntry: () => 'select_type',
52
+ }))
53
+ jest.mock('../../../../hooks/use_current_person', () => ({
54
+ useCurrentPersonCache: () => ({ update: jest.fn() }),
55
+ }))
56
+ jest.mock('../../../../hooks/use_conversations_actions', () => ({
57
+ useMarkAllRead: () => ({ markAllRead: jest.fn(), isPending: false }),
58
+ }))
59
+ jest.mock('../../../../hooks/use_new_conversation_from_filter', () => ({
60
+ useNewConversationFromFilter: () => undefined,
61
+ }))
62
+ jest.mock('../../../../hooks/use_search_enabled', () => ({
63
+ useSearchEnabled: () => mockSearchEnabled(),
64
+ }))
65
+
66
+ const FakeMenuView = ({ actions, onPressAction, children }: MenuViewProps) => {
67
+ const flatten = (items: MenuAction[]): MenuAction[] =>
68
+ items.flatMap(item => (item.displayInline && item.subactions ? item.subactions : [item]))
69
+
70
+ return (
71
+ <>
72
+ {children}
73
+ {flatten(actions).map(action => (
74
+ <Pressable
75
+ key={action.id}
76
+ accessibilityRole="menuitem"
77
+ accessibilityLabel={action.title}
78
+ accessibilityState={{ selected: action.state === 'on' }}
79
+ onPress={() => onPressAction?.({ nativeEvent: { event: action.id ?? action.title } })}
80
+ >
81
+ <Text>{action.title}</Text>
82
+ </Pressable>
83
+ ))}
84
+ </>
85
+ )
86
+ }
87
+
88
+ beforeAll(() => {
89
+ ChatAdapters.configure({
90
+ clipboard: new ClipboardAdapter({
91
+ getStringAsync: async () => '',
92
+ setStringAsync: async () => {},
93
+ }),
94
+ audio: new AudioAdapter({ useAudio: () => ({}) as any }),
95
+ video: new VideoAdapter({ Player: (() => null) as any }),
96
+ imagePicker: new ImagePickerAdapter({
97
+ openCameraAsync: async () => ({ canceled: true, assets: null }),
98
+ openImageLibraryAsync: async () => ({ canceled: true, assets: null }),
99
+ }),
100
+ menu: new MenuAdapter({ View: FakeMenuView }),
101
+ })
102
+ })
103
+
104
+ const setCapabilities = ({ groups, teams }: { groups: boolean; teams: boolean }) => {
105
+ mockCanDisplayGroups.mockImplementation(({ source_type }: { source_type: string }) =>
106
+ source_type === 'Group' ? groups : teams
107
+ )
108
+ }
109
+
110
+ const setRouteParams = (params: Record<string, unknown>) => {
111
+ Object.keys(mockRouteParams).forEach(key => delete mockRouteParams[key])
112
+ Object.assign(mockRouteParams, params)
113
+ }
114
+
115
+ const menuItemNames = () =>
116
+ screen.getAllByRole('menuitem').map(item => item.props.accessibilityLabel)
117
+
118
+ describe('FilterDropdownListHeaderComponent', () => {
119
+ beforeEach(() => {
120
+ mockNavigate.mockClear()
121
+ mockSetParams.mockClear()
122
+ mockSearchEnabled.mockReturnValue(true)
123
+ mockCanBrowseArchived.mockReturnValue(false)
124
+ setCapabilities({ groups: true, teams: true })
125
+ setRouteParams({})
126
+ })
127
+
128
+ it('offers every filter the person can use, then More filters', () => {
129
+ render(<FilterDropdownListHeaderComponent />)
130
+
131
+ expect(menuItemNames()).toEqual(['All', 'Groups', 'Teams', 'DMs', 'More filters'])
132
+ expect(screen.getByRole('button', { name: 'All conversations' })).toBeTruthy()
133
+ })
134
+
135
+ it('leaves out Teams and More filters when only DMs are filterable', () => {
136
+ setCapabilities({ groups: false, teams: false })
137
+
138
+ render(<FilterDropdownListHeaderComponent />)
139
+
140
+ expect(menuItemNames()).toEqual(['All', 'DMs'])
141
+ })
142
+
143
+ it('selecting Groups filters to group conversations', () => {
144
+ render(<FilterDropdownListHeaderComponent />)
145
+
146
+ fireEvent.press(screen.getByRole('menuitem', { name: 'Groups' }))
147
+
148
+ expect(mockSetParams).toHaveBeenCalledWith(
149
+ expect.objectContaining({ group_source_app_name: 'Groups', show_direct_messages: undefined })
150
+ )
151
+ })
152
+
153
+ it('labels the trigger with the active filter and checks it', () => {
154
+ setRouteParams({ group_source_app_name: 'Services' })
155
+
156
+ render(<FilterDropdownListHeaderComponent />)
157
+
158
+ expect(screen.getByRole('button', { name: 'Teams' })).toBeTruthy()
159
+ expect(screen.getByRole('menuitem', { name: 'Teams' }).props.accessibilityState.selected).toBe(
160
+ true
161
+ )
162
+ })
163
+
164
+ it("labels the trigger with the group's name when filtered to one group from More filters", () => {
165
+ mockGroupsByGraphId['Group/1'] = { name: 'Youth Group' }
166
+ setRouteParams({ chat_group_graph_id: 'Group/1' })
167
+
168
+ render(<FilterDropdownListHeaderComponent />)
169
+
170
+ expect(screen.getByRole('button', { name: 'Youth Group' })).toBeTruthy()
171
+ })
172
+
173
+ it('falls back to the route title while the group is still loading', () => {
174
+ delete mockGroupsByGraphId['Group/2']
175
+ setRouteParams({ chat_group_graph_id: 'Group/2', title: 'Youth Group' })
176
+
177
+ render(<FilterDropdownListHeaderComponent />)
178
+
179
+ expect(screen.getByRole('button', { name: 'Youth Group' })).toBeTruthy()
180
+ })
181
+
182
+ it('offers More filters when archived conversations are the only extra filter', () => {
183
+ setCapabilities({ groups: false, teams: false })
184
+ mockCanBrowseArchived.mockReturnValue(true)
185
+
186
+ render(<FilterDropdownListHeaderComponent />)
187
+
188
+ expect(menuItemNames()).toEqual(['All', 'DMs', 'More filters'])
189
+ })
190
+
191
+ it('labels the archived list and carries it into More filters', () => {
192
+ setRouteParams({ show_archived: true })
193
+
194
+ render(<FilterDropdownListHeaderComponent />)
195
+
196
+ expect(screen.getByRole('button', { name: 'Archived' })).toBeTruthy()
197
+
198
+ fireEvent.press(screen.getByRole('menuitem', { name: 'More filters' }))
199
+
200
+ expect(mockNavigate).toHaveBeenCalledWith(
201
+ 'ConversationFilters',
202
+ expect.objectContaining({ show_archived: true })
203
+ )
204
+ })
205
+
206
+ it('hides Mark all read on the archived list', () => {
207
+ const view = render(<FilterDropdownListHeaderComponent />)
208
+
209
+ expect(view.getByText('Mark all read')).toBeTruthy()
210
+
211
+ setRouteParams({ show_archived: true })
212
+ view.rerender(<FilterDropdownListHeaderComponent />)
213
+
214
+ expect(view.queryByText('Mark all read')).toBeNull()
215
+ })
216
+
217
+ it('leaves the archived list when another filter is picked', () => {
218
+ setRouteParams({ show_archived: true })
219
+
220
+ render(<FilterDropdownListHeaderComponent />)
221
+
222
+ fireEvent.press(screen.getByRole('menuitem', { name: 'Groups' }))
223
+
224
+ expect(mockSetParams).toHaveBeenCalledWith(
225
+ expect.objectContaining({ group_source_app_name: 'Groups', show_archived: undefined })
226
+ )
227
+ })
228
+
229
+ it('More filters opens the filters sheet', () => {
230
+ render(<FilterDropdownListHeaderComponent />)
231
+
232
+ fireEvent.press(screen.getByRole('menuitem', { name: 'More filters' }))
233
+
234
+ expect(mockNavigate).toHaveBeenCalledWith('ConversationFilters', expect.any(Object))
235
+ })
236
+
237
+ it('shows the search button only when search is enabled', () => {
238
+ mockSearchEnabled.mockReturnValue(false)
239
+ const view = render(<FilterDropdownListHeaderComponent />)
240
+
241
+ expect(view.queryByTestId('conversations-search-button')).toBeNull()
242
+
243
+ mockSearchEnabled.mockReturnValue(true)
244
+ view.rerender(<FilterDropdownListHeaderComponent />)
245
+
246
+ fireEvent.press(view.getByTestId('conversations-search-button'))
247
+
248
+ expect(mockNavigate).toHaveBeenCalledWith('Search')
249
+ })
250
+
251
+ it('renders the search button after the other actions', () => {
252
+ const view = render(<FilterDropdownListHeaderComponent />)
253
+
254
+ const buttons = view.getAllByRole('button').map(button => button.props.accessibilityLabel)
255
+
256
+ expect(buttons.indexOf('Search')).toBeGreaterThan(buttons.indexOf('Chat settings'))
257
+ expect(buttons.indexOf('Search')).toBeGreaterThan(buttons.indexOf('New conversation'))
258
+ })
259
+ })