@planningcenter/chat-react-native 3.38.0-rc.1 → 3.38.0-rc.11

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 (223) hide show
  1. package/build/components/conversation/jump_to_bottom_button.d.ts +1 -2
  2. package/build/components/conversation/jump_to_bottom_button.d.ts.map +1 -1
  3. package/build/components/conversation/jump_to_bottom_button.js +7 -39
  4. package/build/components/conversation/jump_to_bottom_button.js.map +1 -1
  5. package/build/components/conversation/message_list.d.ts +10 -0
  6. package/build/components/conversation/message_list.d.ts.map +1 -0
  7. package/build/components/conversation/message_list.js +13 -0
  8. package/build/components/conversation/message_list.js.map +1 -0
  9. package/build/components/conversation/reply_shadow_message.d.ts +2 -1
  10. package/build/components/conversation/reply_shadow_message.d.ts.map +1 -1
  11. package/build/components/conversation/reply_shadow_message.js.map +1 -1
  12. package/build/components/display/conversation_avatar.d.ts +2 -1
  13. package/build/components/display/conversation_avatar.d.ts.map +1 -1
  14. package/build/components/display/conversation_avatar.js +6 -5
  15. package/build/components/display/conversation_avatar.js.map +1 -1
  16. package/build/components/display/emoji_avatar.d.ts +3 -1
  17. package/build/components/display/emoji_avatar.d.ts.map +1 -1
  18. package/build/components/display/emoji_avatar.js +2 -2
  19. package/build/components/display/emoji_avatar.js.map +1 -1
  20. package/build/components/display/icon_avatar.d.ts +3 -1
  21. package/build/components/display/icon_avatar.d.ts.map +1 -1
  22. package/build/components/display/icon_avatar.js +2 -2
  23. package/build/components/display/icon_avatar.js.map +1 -1
  24. package/build/contexts/conversation_context.d.ts +1 -8
  25. package/build/contexts/conversation_context.d.ts.map +1 -1
  26. package/build/contexts/conversation_context.js +3 -21
  27. package/build/contexts/conversation_context.js.map +1 -1
  28. package/build/hooks/groups/use_group_chat_conversation_payload.d.ts.map +1 -1
  29. package/build/hooks/groups/use_group_chat_conversation_payload.js +1 -0
  30. package/build/hooks/groups/use_group_chat_conversation_payload.js.map +1 -1
  31. package/build/hooks/index.d.ts +1 -0
  32. package/build/hooks/index.d.ts.map +1 -1
  33. package/build/hooks/index.js +1 -0
  34. package/build/hooks/index.js.map +1 -1
  35. package/build/hooks/use_conversation_messages.d.ts +6 -15
  36. package/build/hooks/use_conversation_messages.d.ts.map +1 -1
  37. package/build/hooks/use_conversation_messages.js +9 -62
  38. package/build/hooks/use_conversation_messages.js.map +1 -1
  39. package/build/hooks/use_conversation_messages_jolt_events.d.ts.map +1 -1
  40. package/build/hooks/use_conversation_messages_jolt_events.js +4 -4
  41. package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
  42. package/build/hooks/use_conversations_actions.d.ts +0 -5
  43. package/build/hooks/use_conversations_actions.d.ts.map +1 -1
  44. package/build/hooks/use_conversations_actions.js +0 -12
  45. package/build/hooks/use_conversations_actions.js.map +1 -1
  46. package/build/hooks/use_features.d.ts +0 -1
  47. package/build/hooks/use_features.d.ts.map +1 -1
  48. package/build/hooks/use_features.js +0 -1
  49. package/build/hooks/use_features.js.map +1 -1
  50. package/build/hooks/use_mark_latest_message_read.d.ts +1 -1
  51. package/build/hooks/use_mark_latest_message_read.d.ts.map +1 -1
  52. package/build/hooks/use_mark_latest_message_read.js +1 -17
  53. package/build/hooks/use_mark_latest_message_read.js.map +1 -1
  54. package/build/hooks/use_preview_avatar_diameter.d.ts +2 -0
  55. package/build/hooks/use_preview_avatar_diameter.d.ts.map +1 -0
  56. package/build/hooks/use_preview_avatar_diameter.js +11 -0
  57. package/build/hooks/use_preview_avatar_diameter.js.map +1 -0
  58. package/build/hooks/use_suspense_api.d.ts +0 -1
  59. package/build/hooks/use_suspense_api.d.ts.map +1 -1
  60. package/build/hooks/use_suspense_api.js +1 -1
  61. package/build/hooks/use_suspense_api.js.map +1 -1
  62. package/build/jest.js +1 -1
  63. package/build/jest.js.map +1 -1
  64. package/build/screens/avatar_picker/avatar_picker_screen.d.ts.map +1 -1
  65. package/build/screens/avatar_picker/avatar_picker_screen.js +11 -9
  66. package/build/screens/avatar_picker/avatar_picker_screen.js.map +1 -1
  67. package/build/screens/avatar_picker/avatar_preview.d.ts.map +1 -1
  68. package/build/screens/avatar_picker/avatar_preview.js +13 -5
  69. package/build/screens/avatar_picker/avatar_preview.js.map +1 -1
  70. package/build/screens/avatar_picker/emoji_tab.d.ts.map +1 -1
  71. package/build/screens/avatar_picker/emoji_tab.js +3 -7
  72. package/build/screens/avatar_picker/emoji_tab.js.map +1 -1
  73. package/build/screens/avatar_picker/upload_tab.d.ts.map +1 -1
  74. package/build/screens/avatar_picker/upload_tab.js +2 -1
  75. package/build/screens/avatar_picker/upload_tab.js.map +1 -1
  76. package/build/screens/conversation_details_screen.d.ts.map +1 -1
  77. package/build/screens/conversation_details_screen.js +5 -2
  78. package/build/screens/conversation_details_screen.js.map +1 -1
  79. package/build/screens/conversation_filter_recipients/components/header_row.d.ts.map +1 -1
  80. package/build/screens/conversation_filter_recipients/components/header_row.js +3 -2
  81. package/build/screens/conversation_filter_recipients/components/header_row.js.map +1 -1
  82. package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.d.ts.map +1 -1
  83. package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.js +47 -18
  84. package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.js.map +1 -1
  85. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts +2 -1
  86. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts.map +1 -1
  87. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js +23 -26
  88. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js.map +1 -1
  89. package/build/screens/conversation_filter_recipients/types.d.ts +1 -1
  90. package/build/screens/conversation_filter_recipients/types.d.ts.map +1 -1
  91. package/build/screens/conversation_filter_recipients/types.js.map +1 -1
  92. package/build/screens/conversation_screen.d.ts +0 -1
  93. package/build/screens/conversation_screen.d.ts.map +1 -1
  94. package/build/screens/conversation_screen.js +45 -96
  95. package/build/screens/conversation_screen.js.map +1 -1
  96. package/build/screens/conversation_select_recipients/components/recipient_link_row.d.ts +1 -1
  97. package/build/screens/conversation_select_recipients/components/recipient_link_row.d.ts.map +1 -1
  98. package/build/screens/conversation_select_recipients/components/recipient_link_row.js +3 -3
  99. package/build/screens/conversation_select_recipients/components/recipient_link_row.js.map +1 -1
  100. package/build/screens/conversation_select_recipients/components/team_recipient_row.d.ts.map +1 -1
  101. package/build/screens/conversation_select_recipients/components/team_recipient_row.js +1 -1
  102. package/build/screens/conversation_select_recipients/components/team_recipient_row.js.map +1 -1
  103. package/build/screens/team_conversation_screen.d.ts.map +1 -1
  104. package/build/screens/team_conversation_screen.js +24 -1
  105. package/build/screens/team_conversation_screen.js.map +1 -1
  106. package/build/utils/cache/messages_cache.d.ts +0 -1
  107. package/build/utils/cache/messages_cache.d.ts.map +1 -1
  108. package/build/utils/cache/messages_cache.js +0 -4
  109. package/build/utils/cache/messages_cache.js.map +1 -1
  110. package/build/utils/client/client.d.ts +1 -1
  111. package/build/utils/client/client.d.ts.map +1 -1
  112. package/build/utils/client/client.js +7 -6
  113. package/build/utils/client/client.js.map +1 -1
  114. package/build/utils/client/instrumented_fetch.js +3 -5
  115. package/build/utils/client/instrumented_fetch.js.map +1 -1
  116. package/build/utils/group_messages.d.ts +2 -9
  117. package/build/utils/group_messages.d.ts.map +1 -1
  118. package/build/utils/group_messages.js +1 -20
  119. package/build/utils/group_messages.js.map +1 -1
  120. package/package.json +4 -4
  121. package/src/__tests__/hooks/use_group_chat_conversation_payload.test.tsx +50 -0
  122. package/src/__tests__/jest.ts +1 -1
  123. package/src/__tests__/utils/client.ts +32 -0
  124. package/src/components/conversation/__tests__/message_list.test.tsx +14 -0
  125. package/src/components/conversation/jump_to_bottom_button.tsx +8 -57
  126. package/src/components/conversation/message_list.tsx +42 -0
  127. package/src/components/conversation/reply_shadow_message.tsx +1 -1
  128. package/src/components/display/conversation_avatar.tsx +7 -5
  129. package/src/components/display/emoji_avatar.tsx +10 -2
  130. package/src/components/display/icon_avatar.tsx +10 -2
  131. package/src/contexts/conversation_context.tsx +2 -30
  132. package/src/hooks/groups/use_group_chat_conversation_payload.ts +1 -0
  133. package/src/hooks/index.ts +1 -0
  134. package/src/hooks/use_conversation_messages.ts +20 -120
  135. package/src/hooks/use_conversation_messages_jolt_events.ts +3 -4
  136. package/src/hooks/use_conversations_actions.ts +0 -15
  137. package/src/hooks/use_features.ts +0 -1
  138. package/src/hooks/use_mark_latest_message_read.ts +2 -16
  139. package/src/hooks/use_preview_avatar_diameter.ts +12 -0
  140. package/src/hooks/use_suspense_api.ts +1 -1
  141. package/src/jest.ts +1 -1
  142. package/src/screens/avatar_picker/avatar_picker_screen.tsx +25 -9
  143. package/src/screens/avatar_picker/avatar_preview.tsx +14 -5
  144. package/src/screens/avatar_picker/emoji_tab.tsx +3 -6
  145. package/src/screens/avatar_picker/upload_tab.tsx +2 -0
  146. package/src/screens/conversation_details_screen.tsx +10 -1
  147. package/src/screens/conversation_filter_recipients/components/header_row.tsx +3 -2
  148. package/src/screens/conversation_filter_recipients/hooks/__tests__/use_service_types_with_teams.test.ts +108 -0
  149. package/src/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.tsx +46 -19
  150. package/src/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.ts +31 -29
  151. package/src/screens/conversation_filter_recipients/types.tsx +1 -1
  152. package/src/screens/conversation_screen.tsx +69 -186
  153. package/src/screens/conversation_select_recipients/components/recipient_link_row.tsx +6 -4
  154. package/src/screens/conversation_select_recipients/components/team_recipient_row.tsx +2 -1
  155. package/src/screens/team_conversation_screen.tsx +33 -1
  156. package/src/utils/__tests__/group_messages.test.ts +0 -71
  157. package/src/utils/cache/messages_cache.ts +0 -5
  158. package/src/utils/client/__tests__/instrumented_fetch.test.ts +9 -5
  159. package/src/utils/client/client.ts +9 -7
  160. package/src/utils/client/instrumented_fetch.ts +3 -6
  161. package/src/utils/group_messages.ts +2 -42
  162. package/build/components/conversation/unread_divider.d.ts +0 -6
  163. package/build/components/conversation/unread_divider.d.ts.map +0 -1
  164. package/build/components/conversation/unread_divider.js +0 -59
  165. package/build/components/conversation/unread_divider.js.map +0 -1
  166. package/build/hooks/use_flat_list_viewability.d.ts +0 -20
  167. package/build/hooks/use_flat_list_viewability.d.ts.map +0 -1
  168. package/build/hooks/use_flat_list_viewability.js +0 -30
  169. package/build/hooks/use_flat_list_viewability.js.map +0 -1
  170. package/build/hooks/use_jump_to_bottom_action.d.ts +0 -9
  171. package/build/hooks/use_jump_to_bottom_action.d.ts.map +0 -1
  172. package/build/hooks/use_jump_to_bottom_action.js +0 -62
  173. package/build/hooks/use_jump_to_bottom_action.js.map +0 -1
  174. package/build/hooks/use_jump_to_unread_anchor.d.ts +0 -20
  175. package/build/hooks/use_jump_to_unread_anchor.d.ts.map +0 -1
  176. package/build/hooks/use_jump_to_unread_anchor.js +0 -53
  177. package/build/hooks/use_jump_to_unread_anchor.js.map +0 -1
  178. package/build/hooks/use_jump_to_unread_gates.d.ts +0 -5
  179. package/build/hooks/use_jump_to_unread_gates.d.ts.map +0 -1
  180. package/build/hooks/use_jump_to_unread_gates.js +0 -10
  181. package/build/hooks/use_jump_to_unread_gates.js.map +0 -1
  182. package/build/hooks/use_scroll_tracking.d.ts +0 -13
  183. package/build/hooks/use_scroll_tracking.d.ts.map +0 -1
  184. package/build/hooks/use_scroll_tracking.js +0 -45
  185. package/build/hooks/use_scroll_tracking.js.map +0 -1
  186. package/build/hooks/use_track_highest_seen_message.d.ts +0 -4
  187. package/build/hooks/use_track_highest_seen_message.d.ts.map +0 -1
  188. package/build/hooks/use_track_highest_seen_message.js +0 -35
  189. package/build/hooks/use_track_highest_seen_message.js.map +0 -1
  190. package/build/utils/conversation_messages.d.ts +0 -10
  191. package/build/utils/conversation_messages.d.ts.map +0 -1
  192. package/build/utils/conversation_messages.js +0 -22
  193. package/build/utils/conversation_messages.js.map +0 -1
  194. package/build/utils/highest_seen_tracker.d.ts +0 -12
  195. package/build/utils/highest_seen_tracker.d.ts.map +0 -1
  196. package/build/utils/highest_seen_tracker.js +0 -37
  197. package/build/utils/highest_seen_tracker.js.map +0 -1
  198. package/build/utils/message_viewability.d.ts +0 -24
  199. package/build/utils/message_viewability.d.ts.map +0 -1
  200. package/build/utils/message_viewability.js +0 -29
  201. package/build/utils/message_viewability.js.map +0 -1
  202. package/build/utils/unread_divider_helpers.d.ts +0 -18
  203. package/build/utils/unread_divider_helpers.d.ts.map +0 -1
  204. package/build/utils/unread_divider_helpers.js +0 -13
  205. package/build/utils/unread_divider_helpers.js.map +0 -1
  206. package/src/__tests__/hooks/use_conversation_messages.test.tsx +0 -109
  207. package/src/__tests__/hooks/use_mark_latest_message_read.test.tsx +0 -154
  208. package/src/__tests__/utils/cache/messages_cache.test.ts +0 -54
  209. package/src/components/conversation/unread_divider.tsx +0 -90
  210. package/src/hooks/use_flat_list_viewability.ts +0 -50
  211. package/src/hooks/use_jump_to_bottom_action.ts +0 -75
  212. package/src/hooks/use_jump_to_unread_anchor.ts +0 -68
  213. package/src/hooks/use_jump_to_unread_gates.ts +0 -10
  214. package/src/hooks/use_scroll_tracking.ts +0 -64
  215. package/src/hooks/use_track_highest_seen_message.ts +0 -43
  216. package/src/utils/__tests__/conversation_messages.test.ts +0 -105
  217. package/src/utils/__tests__/highest_seen_tracker.test.ts +0 -82
  218. package/src/utils/__tests__/message_viewability.test.ts +0 -168
  219. package/src/utils/__tests__/unread_divider_helpers.test.ts +0 -85
  220. package/src/utils/conversation_messages.ts +0 -37
  221. package/src/utils/highest_seen_tracker.ts +0 -42
  222. package/src/utils/message_viewability.ts +0 -49
  223. package/src/utils/unread_divider_helpers.ts +0 -25
@@ -1,13 +0,0 @@
1
- import type { NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
2
- interface UseScrollTrackingArgs {
3
- hasMoreNewerMessages: boolean;
4
- isFetchingNewerMessages: boolean;
5
- fetchNewerMessages: () => void;
6
- cancelFetchNewerMessages: () => void;
7
- }
8
- export declare function useScrollTracking({ hasMoreNewerMessages, isFetchingNewerMessages, fetchNewerMessages, cancelFetchNewerMessages, }: UseScrollTrackingArgs): {
9
- onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
10
- showJumpToBottomButton: boolean;
11
- };
12
- export {};
13
- //# sourceMappingURL=use_scroll_tracking.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"use_scroll_tracking.d.ts","sourceRoot":"","sources":["../../src/hooks/use_scroll_tracking.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAO3E,UAAU,qBAAqB;IAC7B,oBAAoB,EAAE,OAAO,CAAA;IAC7B,uBAAuB,EAAE,OAAO,CAAA;IAChC,kBAAkB,EAAE,MAAM,IAAI,CAAA;IAC9B,wBAAwB,EAAE,MAAM,IAAI,CAAA;CACrC;AAED,wBAAgB,iBAAiB,CAAC,EAChC,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,wBAAwB,GACzB,EAAE,qBAAqB;sBAgBZ,oBAAoB,CAAC,iBAAiB,CAAC;;EA2BlD"}
@@ -1,45 +0,0 @@
1
- import { useCallback, useEffect, useRef, useState } from 'react';
2
- import { useConversationContext } from '../contexts/conversation_context';
3
- const JUMP_TO_BOTTOM_OFFSET_THRESHOLD = 200;
4
- const AT_BOTTOM_OFFSET_TOLERANCE = 5;
5
- const FETCH_NEWER_OFFSET_THRESHOLD = 600;
6
- export function useScrollTracking({ hasMoreNewerMessages, isFetchingNewerMessages, fetchNewerMessages, cancelFetchNewerMessages, }) {
7
- const { atEndOfMessageHistory, setAtEndOfMessageHistory } = useConversationContext();
8
- const [showJumpToBottomButton, setShowJumpToBottomButton] = useState(false);
9
- const withinBoundaryRef = useRef(false);
10
- useEffect(() => {
11
- return () => {
12
- cancelFetchNewerMessages();
13
- };
14
- }, [cancelFetchNewerMessages]);
15
- useEffect(() => {
16
- if (!isFetchingNewerMessages)
17
- withinBoundaryRef.current = false;
18
- }, [isFetchingNewerMessages]);
19
- const onScroll = useCallback((event) => {
20
- const offsetY = event.nativeEvent.contentOffset.y;
21
- setShowJumpToBottomButton(offsetY > JUMP_TO_BOTTOM_OFFSET_THRESHOLD);
22
- const atBottom = offsetY < AT_BOTTOM_OFFSET_TOLERANCE;
23
- const atEnd = atBottom && !hasMoreNewerMessages;
24
- if (atEnd !== atEndOfMessageHistory)
25
- setAtEndOfMessageHistory(atEnd);
26
- if (offsetY >= FETCH_NEWER_OFFSET_THRESHOLD) {
27
- withinBoundaryRef.current = false;
28
- return;
29
- }
30
- if (withinBoundaryRef.current)
31
- return;
32
- if (!hasMoreNewerMessages || isFetchingNewerMessages)
33
- return;
34
- withinBoundaryRef.current = true;
35
- fetchNewerMessages();
36
- }, [
37
- hasMoreNewerMessages,
38
- isFetchingNewerMessages,
39
- fetchNewerMessages,
40
- atEndOfMessageHistory,
41
- setAtEndOfMessageHistory,
42
- ]);
43
- return { onScroll, showJumpToBottomButton };
44
- }
45
- //# sourceMappingURL=use_scroll_tracking.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"use_scroll_tracking.js","sourceRoot":"","sources":["../../src/hooks/use_scroll_tracking.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEhE,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AAEzE,MAAM,+BAA+B,GAAG,GAAG,CAAA;AAC3C,MAAM,0BAA0B,GAAG,CAAC,CAAA;AACpC,MAAM,4BAA4B,GAAG,GAAG,CAAA;AASxC,MAAM,UAAU,iBAAiB,CAAC,EAChC,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,wBAAwB,GACF;IACtB,MAAM,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,GAAG,sBAAsB,EAAE,CAAA;IACpF,MAAM,CAAC,sBAAsB,EAAE,yBAAyB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC3E,MAAM,iBAAiB,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAEvC,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE;YACV,wBAAwB,EAAE,CAAA;QAC5B,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,wBAAwB,CAAC,CAAC,CAAA;IAE9B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,uBAAuB;YAAE,iBAAiB,CAAC,OAAO,GAAG,KAAK,CAAA;IACjE,CAAC,EAAE,CAAC,uBAAuB,CAAC,CAAC,CAAA;IAE7B,MAAM,QAAQ,GAAG,WAAW,CAC1B,CAAC,KAA8C,EAAE,EAAE;QACjD,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAA;QACjD,yBAAyB,CAAC,OAAO,GAAG,+BAA+B,CAAC,CAAA;QAEpE,MAAM,QAAQ,GAAG,OAAO,GAAG,0BAA0B,CAAA;QACrD,MAAM,KAAK,GAAG,QAAQ,IAAI,CAAC,oBAAoB,CAAA;QAC/C,IAAI,KAAK,KAAK,qBAAqB;YAAE,wBAAwB,CAAC,KAAK,CAAC,CAAA;QAEpE,IAAI,OAAO,IAAI,4BAA4B,EAAE,CAAC;YAC5C,iBAAiB,CAAC,OAAO,GAAG,KAAK,CAAA;YACjC,OAAM;QACR,CAAC;QACD,IAAI,iBAAiB,CAAC,OAAO;YAAE,OAAM;QACrC,IAAI,CAAC,oBAAoB,IAAI,uBAAuB;YAAE,OAAM;QAC5D,iBAAiB,CAAC,OAAO,GAAG,IAAI,CAAA;QAChC,kBAAkB,EAAE,CAAA;IACtB,CAAC,EACD;QACE,oBAAoB;QACpB,uBAAuB;QACvB,kBAAkB;QAClB,qBAAqB;QACrB,wBAAwB;KACzB,CACF,CAAA;IAED,OAAO,EAAE,QAAQ,EAAE,sBAAsB,EAAE,CAAA;AAC7C,CAAC","sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react'\nimport type { NativeScrollEvent, NativeSyntheticEvent } from 'react-native'\nimport { useConversationContext } from '../contexts/conversation_context'\n\nconst JUMP_TO_BOTTOM_OFFSET_THRESHOLD = 200\nconst AT_BOTTOM_OFFSET_TOLERANCE = 5\nconst FETCH_NEWER_OFFSET_THRESHOLD = 600\n\ninterface UseScrollTrackingArgs {\n hasMoreNewerMessages: boolean\n isFetchingNewerMessages: boolean\n fetchNewerMessages: () => void\n cancelFetchNewerMessages: () => void\n}\n\nexport function useScrollTracking({\n hasMoreNewerMessages,\n isFetchingNewerMessages,\n fetchNewerMessages,\n cancelFetchNewerMessages,\n}: UseScrollTrackingArgs) {\n const { atEndOfMessageHistory, setAtEndOfMessageHistory } = useConversationContext()\n const [showJumpToBottomButton, setShowJumpToBottomButton] = useState(false)\n const withinBoundaryRef = useRef(false)\n\n useEffect(() => {\n return () => {\n cancelFetchNewerMessages()\n }\n }, [cancelFetchNewerMessages])\n\n useEffect(() => {\n if (!isFetchingNewerMessages) withinBoundaryRef.current = false\n }, [isFetchingNewerMessages])\n\n const onScroll = useCallback(\n (event: NativeSyntheticEvent<NativeScrollEvent>) => {\n const offsetY = event.nativeEvent.contentOffset.y\n setShowJumpToBottomButton(offsetY > JUMP_TO_BOTTOM_OFFSET_THRESHOLD)\n\n const atBottom = offsetY < AT_BOTTOM_OFFSET_TOLERANCE\n const atEnd = atBottom && !hasMoreNewerMessages\n if (atEnd !== atEndOfMessageHistory) setAtEndOfMessageHistory(atEnd)\n\n if (offsetY >= FETCH_NEWER_OFFSET_THRESHOLD) {\n withinBoundaryRef.current = false\n return\n }\n if (withinBoundaryRef.current) return\n if (!hasMoreNewerMessages || isFetchingNewerMessages) return\n withinBoundaryRef.current = true\n fetchNewerMessages()\n },\n [\n hasMoreNewerMessages,\n isFetchingNewerMessages,\n fetchNewerMessages,\n atEndOfMessageHistory,\n setAtEndOfMessageHistory,\n ]\n )\n\n return { onScroll, showJumpToBottomButton }\n}\n"]}
@@ -1,4 +0,0 @@
1
- export declare function useTrackHighestSeenMessage(): {
2
- onMessageSeen: (sortKey: string) => void;
3
- };
4
- //# sourceMappingURL=use_track_highest_seen_message.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"use_track_highest_seen_message.d.ts","sourceRoot":"","sources":["../../src/hooks/use_track_highest_seen_message.ts"],"names":[],"mappings":"AAOA,wBAAgB,0BAA0B;6BAY5B,MAAM;EAuBnB"}
@@ -1,35 +0,0 @@
1
- import { useCallback, useEffect, useMemo } from 'react';
2
- import { AppState } from 'react-native';
3
- import { useConversationContext } from '../contexts/conversation_context';
4
- import { makeHighestSeenTracker } from '../utils/highest_seen_tracker';
5
- import { useConversationsMarkReadUpTo } from './use_conversations_actions';
6
- import { useJumpToUnreadGates } from './use_jump_to_unread_gates';
7
- export function useTrackHighestSeenMessage() {
8
- const { conversationId, currentPageReplyRootId } = useConversationContext();
9
- const { jumpToUnreadActive } = useJumpToUnreadGates();
10
- const enabled = jumpToUnreadActive && !currentPageReplyRootId;
11
- const { mutate: markReadUpTo } = useConversationsMarkReadUpTo({ conversationId });
12
- const tracker = useMemo(() => makeHighestSeenTracker(conversationId, ({ sortKey }) => markReadUpTo({ sortKey })), [conversationId, markReadUpTo]);
13
- const onMessageSeen = useCallback((sortKey) => {
14
- if (!enabled)
15
- return;
16
- tracker.onSeen(sortKey);
17
- }, [enabled, tracker]);
18
- useEffect(() => {
19
- if (!enabled)
20
- return;
21
- const sub = AppState.addEventListener('change', state => {
22
- if (state !== 'active')
23
- tracker.flushNow();
24
- });
25
- return () => sub.remove();
26
- }, [enabled, tracker]);
27
- useEffect(() => {
28
- return () => {
29
- tracker.flushNow();
30
- tracker.cancel();
31
- };
32
- }, [tracker]);
33
- return { onMessageSeen };
34
- }
35
- //# sourceMappingURL=use_track_highest_seen_message.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"use_track_highest_seen_message.js","sourceRoot":"","sources":["../../src/hooks/use_track_highest_seen_message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AACtE,OAAO,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAA;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAEjE,MAAM,UAAU,0BAA0B;IACxC,MAAM,EAAE,cAAc,EAAE,sBAAsB,EAAE,GAAG,sBAAsB,EAAE,CAAA;IAC3E,MAAM,EAAE,kBAAkB,EAAE,GAAG,oBAAoB,EAAE,CAAA;IACrD,MAAM,OAAO,GAAG,kBAAkB,IAAI,CAAC,sBAAsB,CAAA;IAC7D,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,4BAA4B,CAAC,EAAE,cAAc,EAAE,CAAC,CAAA;IAEjF,MAAM,OAAO,GAAG,OAAO,CACrB,GAAG,EAAE,CAAC,sBAAsB,CAAC,cAAc,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,EACxF,CAAC,cAAc,EAAE,YAAY,CAAC,CAC/B,CAAA;IAED,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,OAAe,EAAE,EAAE;QAClB,IAAI,CAAC,OAAO;YAAE,OAAM;QACpB,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACzB,CAAC,EACD,CAAC,OAAO,EAAE,OAAO,CAAC,CACnB,CAAA;IAED,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO;YAAE,OAAM;QACpB,MAAM,GAAG,GAAG,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE;YACtD,IAAI,KAAK,KAAK,QAAQ;gBAAE,OAAO,CAAC,QAAQ,EAAE,CAAA;QAC5C,CAAC,CAAC,CAAA;QACF,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,CAAA;IAC3B,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;IAEtB,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE;YACV,OAAO,CAAC,QAAQ,EAAE,CAAA;YAClB,OAAO,CAAC,MAAM,EAAE,CAAA;QAClB,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IAEb,OAAO,EAAE,aAAa,EAAE,CAAA;AAC1B,CAAC","sourcesContent":["import { useCallback, useEffect, useMemo } from 'react'\nimport { AppState } from 'react-native'\nimport { useConversationContext } from '../contexts/conversation_context'\nimport { makeHighestSeenTracker } from '../utils/highest_seen_tracker'\nimport { useConversationsMarkReadUpTo } from './use_conversations_actions'\nimport { useJumpToUnreadGates } from './use_jump_to_unread_gates'\n\nexport function useTrackHighestSeenMessage() {\n const { conversationId, currentPageReplyRootId } = useConversationContext()\n const { jumpToUnreadActive } = useJumpToUnreadGates()\n const enabled = jumpToUnreadActive && !currentPageReplyRootId\n const { mutate: markReadUpTo } = useConversationsMarkReadUpTo({ conversationId })\n\n const tracker = useMemo(\n () => makeHighestSeenTracker(conversationId, ({ sortKey }) => markReadUpTo({ sortKey })),\n [conversationId, markReadUpTo]\n )\n\n const onMessageSeen = useCallback(\n (sortKey: string) => {\n if (!enabled) return\n tracker.onSeen(sortKey)\n },\n [enabled, tracker]\n )\n\n useEffect(() => {\n if (!enabled) return\n const sub = AppState.addEventListener('change', state => {\n if (state !== 'active') tracker.flushNow()\n })\n return () => sub.remove()\n }, [enabled, tracker])\n\n useEffect(() => {\n return () => {\n tracker.flushNow()\n tracker.cancel()\n }\n }, [tracker])\n\n return { onMessageSeen }\n}\n"]}
@@ -1,10 +0,0 @@
1
- import { ApiCollection, MessageResource } from '../types';
2
- import { RequestData } from './client';
3
- export type MessagesPageParam = Partial<RequestData> & {
4
- order?: 'asc' | 'desc';
5
- };
6
- export declare const anchoredSeedPageParams: (anchor: string) => MessagesPageParam[];
7
- export declare const olderPageParam: (page: ApiCollection<MessageResource>) => MessagesPageParam | undefined;
8
- export declare const newerPageParam: (page: ApiCollection<MessageResource>) => MessagesPageParam | undefined;
9
- export declare const sortAndFilterMessages: (pages: ApiCollection<MessageResource>[]) => MessageResource[];
10
- //# sourceMappingURL=conversation_messages.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"conversation_messages.d.ts","sourceRoot":"","sources":["../../src/utils/conversation_messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAEtC,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG;IACrD,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;CACvB,CAAA;AAED,eAAO,MAAM,sBAAsB,GAAI,QAAQ,MAAM,KAAG,iBAAiB,EAGxE,CAAA;AAED,eAAO,MAAM,cAAc,GACzB,MAAM,aAAa,CAAC,eAAe,CAAC,KACnC,iBAAiB,GAAG,SAItB,CAAA;AAED,eAAO,MAAM,cAAc,GACzB,MAAM,aAAa,CAAC,eAAe,CAAC,KACnC,iBAAiB,GAAG,SAItB,CAAA;AAED,eAAO,MAAM,qBAAqB,GAAI,OAAO,aAAa,CAAC,eAAe,CAAC,EAAE,KAAG,eAAe,EAQjD,CAAA"}
@@ -1,22 +0,0 @@
1
- export const anchoredSeedPageParams = (anchor) => [
2
- { where: { id_gte: anchor }, order: 'asc' },
3
- { where: { id_lt: anchor }, order: 'desc' },
4
- ];
5
- export const olderPageParam = (page) => {
6
- const idLt = page.meta?.next?.idLt;
7
- if (!idLt)
8
- return undefined;
9
- return { where: { id_lt: idLt }, order: 'desc' };
10
- };
11
- export const newerPageParam = (page) => {
12
- const idGt = page.meta?.next?.idGt;
13
- if (!idGt)
14
- return undefined;
15
- return { where: { id_gt: idGt }, order: 'asc' };
16
- };
17
- export const sortAndFilterMessages = (pages) => pages
18
- .flatMap(page => page.data)
19
- .filter(message => (!message.deletedAt || message.replyRootId) &&
20
- (message.attachments?.length || message.text?.length))
21
- .sort((a, b) => -a.id.localeCompare(b.id));
22
- //# sourceMappingURL=conversation_messages.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"conversation_messages.js","sourceRoot":"","sources":["../../src/utils/conversation_messages.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,MAAc,EAAuB,EAAE,CAAC;IAC7E,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;IAC3C,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;CAC5C,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,IAAoC,EACL,EAAE;IACjC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAA;IAClC,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAA;IAC3B,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AAClD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,IAAoC,EACL,EAAE;IACjC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAA;IAClC,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAA;IAC3B,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;AACjD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,KAAuC,EAAqB,EAAE,CAClG,KAAK;KACF,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;KAC1B,MAAM,CACL,OAAO,CAAC,EAAE,CACR,CAAC,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC;IAC3C,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,IAAI,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CACxD;KACA,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA","sourcesContent":["import { ApiCollection, MessageResource } from '../types'\nimport { RequestData } from './client'\n\nexport type MessagesPageParam = Partial<RequestData> & {\n order?: 'asc' | 'desc'\n}\n\nexport const anchoredSeedPageParams = (anchor: string): MessagesPageParam[] => [\n { where: { id_gte: anchor }, order: 'asc' },\n { where: { id_lt: anchor }, order: 'desc' },\n]\n\nexport const olderPageParam = (\n page: ApiCollection<MessageResource>\n): MessagesPageParam | undefined => {\n const idLt = page.meta?.next?.idLt\n if (!idLt) return undefined\n return { where: { id_lt: idLt }, order: 'desc' }\n}\n\nexport const newerPageParam = (\n page: ApiCollection<MessageResource>\n): MessagesPageParam | undefined => {\n const idGt = page.meta?.next?.idGt\n if (!idGt) return undefined\n return { where: { id_gt: idGt }, order: 'asc' }\n}\n\nexport const sortAndFilterMessages = (pages: ApiCollection<MessageResource>[]): MessageResource[] =>\n pages\n .flatMap(page => page.data)\n .filter(\n message =>\n (!message.deletedAt || message.replyRootId) &&\n (message.attachments?.length || message.text?.length)\n )\n .sort((a, b) => -a.id.localeCompare(b.id))\n"]}
@@ -1,12 +0,0 @@
1
- export declare const FLUSH_DELAY_MS = 2000;
2
- type SendFn = (args: {
3
- conversationId: number;
4
- sortKey: string;
5
- }) => void;
6
- export declare function makeHighestSeenTracker(conversationId: number, send: SendFn, flushDelayMs?: number): {
7
- onSeen(sortKey: string): void;
8
- flushNow(): void;
9
- cancel(): void;
10
- };
11
- export {};
12
- //# sourceMappingURL=highest_seen_tracker.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"highest_seen_tracker.d.ts","sourceRoot":"","sources":["../../src/utils/highest_seen_tracker.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,OAAO,CAAA;AAElC,KAAK,MAAM,GAAG,CAAC,IAAI,EAAE;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,CAAA;AAEzE,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,YAAY,GAAE,MAAuB;oBAcnB,MAAM;;;EAoBzB"}
@@ -1,37 +0,0 @@
1
- export const FLUSH_DELAY_MS = 2000;
2
- export function makeHighestSeenTracker(conversationId, send, flushDelayMs = FLUSH_DELAY_MS) {
3
- let highest = null;
4
- let lastSent = null;
5
- let timer = null;
6
- const fire = () => {
7
- timer = null;
8
- if (!highest || highest === lastSent)
9
- return;
10
- lastSent = highest;
11
- send({ conversationId, sortKey: highest });
12
- };
13
- return {
14
- onSeen(sortKey) {
15
- if (highest && sortKey.localeCompare(highest) <= 0)
16
- return;
17
- highest = sortKey;
18
- if (timer)
19
- clearTimeout(timer);
20
- timer = setTimeout(fire, flushDelayMs);
21
- },
22
- flushNow() {
23
- if (timer) {
24
- clearTimeout(timer);
25
- timer = null;
26
- }
27
- fire();
28
- },
29
- cancel() {
30
- if (timer) {
31
- clearTimeout(timer);
32
- timer = null;
33
- }
34
- },
35
- };
36
- }
37
- //# sourceMappingURL=highest_seen_tracker.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"highest_seen_tracker.js","sourceRoot":"","sources":["../../src/utils/highest_seen_tracker.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAA;AAIlC,MAAM,UAAU,sBAAsB,CACpC,cAAsB,EACtB,IAAY,EACZ,eAAuB,cAAc;IAErC,IAAI,OAAO,GAAkB,IAAI,CAAA;IACjC,IAAI,QAAQ,GAAkB,IAAI,CAAA;IAClC,IAAI,KAAK,GAAyC,IAAI,CAAA;IAEtD,MAAM,IAAI,GAAG,GAAG,EAAE;QAChB,KAAK,GAAG,IAAI,CAAA;QACZ,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,QAAQ;YAAE,OAAM;QAC5C,QAAQ,GAAG,OAAO,CAAA;QAClB,IAAI,CAAC,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAA;IAC5C,CAAC,CAAA;IAED,OAAO;QACL,MAAM,CAAC,OAAe;YACpB,IAAI,OAAO,IAAI,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC;gBAAE,OAAM;YAC1D,OAAO,GAAG,OAAO,CAAA;YACjB,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAA;YAC9B,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;QACxC,CAAC;QACD,QAAQ;YACN,IAAI,KAAK,EAAE,CAAC;gBACV,YAAY,CAAC,KAAK,CAAC,CAAA;gBACnB,KAAK,GAAG,IAAI,CAAA;YACd,CAAC;YACD,IAAI,EAAE,CAAA;QACR,CAAC;QACD,MAAM;YACJ,IAAI,KAAK,EAAE,CAAC;gBACV,YAAY,CAAC,KAAK,CAAC,CAAA;gBACnB,KAAK,GAAG,IAAI,CAAA;YACd,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["export const FLUSH_DELAY_MS = 2000\n\ntype SendFn = (args: { conversationId: number; sortKey: string }) => void\n\nexport function makeHighestSeenTracker(\n conversationId: number,\n send: SendFn,\n flushDelayMs: number = FLUSH_DELAY_MS\n) {\n let highest: string | null = null\n let lastSent: string | null = null\n let timer: ReturnType<typeof setTimeout> | null = null\n\n const fire = () => {\n timer = null\n if (!highest || highest === lastSent) return\n lastSent = highest\n send({ conversationId, sortKey: highest })\n }\n\n return {\n onSeen(sortKey: string) {\n if (highest && sortKey.localeCompare(highest) <= 0) return\n highest = sortKey\n if (timer) clearTimeout(timer)\n timer = setTimeout(fire, flushDelayMs)\n },\n flushNow() {\n if (timer) {\n clearTimeout(timer)\n timer = null\n }\n fire()\n },\n cancel() {\n if (timer) {\n clearTimeout(timer)\n timer = null\n }\n },\n }\n}\n"]}
@@ -1,24 +0,0 @@
1
- export interface ViewableEntry<Item> {
2
- key: string;
3
- isViewable: boolean;
4
- item: Item;
5
- }
6
- export interface ViewabilityEvent<Item> {
7
- viewableItems: ViewableEntry<Item>[];
8
- changed: ViewableEntry<Item>[];
9
- userHasScrolled: boolean;
10
- }
11
- export type ViewabilityObserver<Item> = (event: ViewabilityEvent<Item>) => void;
12
- export declare function reportViewableMessages<Item extends {
13
- id?: string;
14
- type?: string;
15
- }>(onMessageSeen: (id: string) => void): ViewabilityObserver<Item>;
16
- export declare function detectDividerExitTowardNewer<Item extends {
17
- id?: string;
18
- type?: string;
19
- }>({ dividerKey, initialMessageId, onExited, }: {
20
- dividerKey: string;
21
- initialMessageId: string | null;
22
- onExited: () => void;
23
- }): ViewabilityObserver<Item>;
24
- //# sourceMappingURL=message_viewability.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"message_viewability.d.ts","sourceRoot":"","sources":["../../src/utils/message_viewability.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa,CAAC,IAAI;IACjC,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,OAAO,CAAA;IACnB,IAAI,EAAE,IAAI,CAAA;CACX;AAED,MAAM,WAAW,gBAAgB,CAAC,IAAI;IACpC,aAAa,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,CAAA;IACpC,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,CAAA;IAC9B,eAAe,EAAE,OAAO,CAAA;CACzB;AAED,MAAM,MAAM,mBAAmB,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,gBAAgB,CAAC,IAAI,CAAC,KAAK,IAAI,CAAA;AAE/E,wBAAgB,sBAAsB,CAAC,IAAI,SAAS;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EAChF,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAClC,mBAAmB,CAAC,IAAI,CAAC,CAS3B;AAED,wBAAgB,4BAA4B,CAAC,IAAI,SAAS;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,EACxF,UAAU,EACV,gBAAgB,EAChB,QAAQ,GACT,EAAE;IACD,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,QAAQ,EAAE,MAAM,IAAI,CAAA;CACrB,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAW5B"}
@@ -1,29 +0,0 @@
1
- import { dividerExitedTowardNewer } from './unread_divider_helpers';
2
- export function reportViewableMessages(onMessageSeen) {
3
- return ({ viewableItems, userHasScrolled }) => {
4
- if (!userHasScrolled)
5
- return;
6
- for (const entry of viewableItems) {
7
- if (entry.item?.type !== 'Message')
8
- continue;
9
- const id = entry.item?.id;
10
- if (typeof id === 'string')
11
- onMessageSeen(id);
12
- }
13
- };
14
- }
15
- export function detectDividerExitTowardNewer({ dividerKey, initialMessageId, onExited, }) {
16
- return ({ viewableItems, changed, userHasScrolled }) => {
17
- if (!userHasScrolled || !initialMessageId)
18
- return;
19
- const exited = dividerExitedTowardNewer({
20
- changed,
21
- viewableItems,
22
- dividerKey,
23
- initialMessageId,
24
- });
25
- if (exited)
26
- onExited();
27
- };
28
- }
29
- //# sourceMappingURL=message_viewability.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"message_viewability.js","sourceRoot":"","sources":["../../src/utils/message_viewability.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AAgBnE,MAAM,UAAU,sBAAsB,CACpC,aAAmC;IAEnC,OAAO,CAAC,EAAE,aAAa,EAAE,eAAe,EAAE,EAAE,EAAE;QAC5C,IAAI,CAAC,eAAe;YAAE,OAAM;QAC5B,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;YAClC,IAAI,KAAK,CAAC,IAAI,EAAE,IAAI,KAAK,SAAS;gBAAE,SAAQ;YAC5C,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,EAAE,CAAA;YACzB,IAAI,OAAO,EAAE,KAAK,QAAQ;gBAAE,aAAa,CAAC,EAAE,CAAC,CAAA;QAC/C,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED,MAAM,UAAU,4BAA4B,CAA8C,EACxF,UAAU,EACV,gBAAgB,EAChB,QAAQ,GAKT;IACC,OAAO,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,EAAE,EAAE;QACrD,IAAI,CAAC,eAAe,IAAI,CAAC,gBAAgB;YAAE,OAAM;QACjD,MAAM,MAAM,GAAG,wBAAwB,CAAC;YACtC,OAAO;YACP,aAAa;YACb,UAAU;YACV,gBAAgB;SACjB,CAAC,CAAA;QACF,IAAI,MAAM;YAAE,QAAQ,EAAE,CAAA;IACxB,CAAC,CAAA;AACH,CAAC","sourcesContent":["import { dividerExitedTowardNewer } from './unread_divider_helpers'\n\nexport interface ViewableEntry<Item> {\n key: string\n isViewable: boolean\n item: Item\n}\n\nexport interface ViewabilityEvent<Item> {\n viewableItems: ViewableEntry<Item>[]\n changed: ViewableEntry<Item>[]\n userHasScrolled: boolean\n}\n\nexport type ViewabilityObserver<Item> = (event: ViewabilityEvent<Item>) => void\n\nexport function reportViewableMessages<Item extends { id?: string; type?: string }>(\n onMessageSeen: (id: string) => void\n): ViewabilityObserver<Item> {\n return ({ viewableItems, userHasScrolled }) => {\n if (!userHasScrolled) return\n for (const entry of viewableItems) {\n if (entry.item?.type !== 'Message') continue\n const id = entry.item?.id\n if (typeof id === 'string') onMessageSeen(id)\n }\n }\n}\n\nexport function detectDividerExitTowardNewer<Item extends { id?: string; type?: string }>({\n dividerKey,\n initialMessageId,\n onExited,\n}: {\n dividerKey: string\n initialMessageId: string | null\n onExited: () => void\n}): ViewabilityObserver<Item> {\n return ({ viewableItems, changed, userHasScrolled }) => {\n if (!userHasScrolled || !initialMessageId) return\n const exited = dividerExitedTowardNewer({\n changed,\n viewableItems,\n dividerKey,\n initialMessageId,\n })\n if (exited) onExited()\n }\n}\n"]}
@@ -1,18 +0,0 @@
1
- type ViewableChangeEntry = {
2
- key: string;
3
- isViewable: boolean;
4
- };
5
- type ViewableItem = {
6
- item: {
7
- id?: string;
8
- type?: string;
9
- };
10
- };
11
- export declare function dividerExitedTowardNewer({ changed, viewableItems, dividerKey, initialMessageId, }: {
12
- changed: ViewableChangeEntry[];
13
- viewableItems: ViewableItem[];
14
- dividerKey: string;
15
- initialMessageId: string;
16
- }): boolean;
17
- export {};
18
- //# sourceMappingURL=unread_divider_helpers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"unread_divider_helpers.d.ts","sourceRoot":"","sources":["../../src/utils/unread_divider_helpers.ts"],"names":[],"mappings":"AAAA,KAAK,mBAAmB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAA;AAC/D,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAA;AAE5D,wBAAgB,wBAAwB,CAAC,EACvC,OAAO,EACP,aAAa,EACb,UAAU,EACV,gBAAgB,GACjB,EAAE;IACD,OAAO,EAAE,mBAAmB,EAAE,CAAA;IAC9B,aAAa,EAAE,YAAY,EAAE,CAAA;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,EAAE,MAAM,CAAA;CACzB,GAAG,OAAO,CAWV"}
@@ -1,13 +0,0 @@
1
- export function dividerExitedTowardNewer({ changed, viewableItems, dividerKey, initialMessageId, }) {
2
- const dividerExited = changed.some(c => c.key === dividerKey && !c.isViewable);
3
- if (!dividerExited)
4
- return false;
5
- const visibleMessageIds = viewableItems
6
- .filter(v => v.item?.type === 'Message')
7
- .map(v => v.item?.id)
8
- .filter((id) => !!id);
9
- if (visibleMessageIds.length === 0)
10
- return false;
11
- return visibleMessageIds.every(id => id.localeCompare(initialMessageId) > 0);
12
- }
13
- //# sourceMappingURL=unread_divider_helpers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"unread_divider_helpers.js","sourceRoot":"","sources":["../../src/utils/unread_divider_helpers.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,wBAAwB,CAAC,EACvC,OAAO,EACP,aAAa,EACb,UAAU,EACV,gBAAgB,GAMjB;IACC,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,UAAU,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAA;IAC9E,IAAI,CAAC,aAAa;QAAE,OAAO,KAAK,CAAA;IAEhC,MAAM,iBAAiB,GAAG,aAAa;SACpC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC;SACvC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,EAAE,EAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IACrC,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAEhD,OAAO,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,aAAa,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAA;AAC9E,CAAC","sourcesContent":["type ViewableChangeEntry = { key: string; isViewable: boolean }\ntype ViewableItem = { item: { id?: string; type?: string } }\n\nexport function dividerExitedTowardNewer({\n changed,\n viewableItems,\n dividerKey,\n initialMessageId,\n}: {\n changed: ViewableChangeEntry[]\n viewableItems: ViewableItem[]\n dividerKey: string\n initialMessageId: string\n}): boolean {\n const dividerExited = changed.some(c => c.key === dividerKey && !c.isViewable)\n if (!dividerExited) return false\n\n const visibleMessageIds = viewableItems\n .filter(v => v.item?.type === 'Message')\n .map(v => v.item?.id)\n .filter((id): id is string => !!id)\n if (visibleMessageIds.length === 0) return false\n\n return visibleMessageIds.every(id => id.localeCompare(initialMessageId) > 0)\n}\n"]}
@@ -1,109 +0,0 @@
1
- import { QueryClientProvider } from '@tanstack/react-query'
2
- import { act, renderHook } from '@testing-library/react-native'
3
- import React, { Suspense } from 'react'
4
- import { buildTestQueryClient } from '../../__utils__/query_client'
5
- import { ConversationContextProvider } from '../../contexts/conversation_context'
6
- import * as useApiClientModule from '../../hooks/use_api_client'
7
- import { useConversationMessages } from '../../hooks/use_conversation_messages'
8
- import { ApiCollection, MessageResource } from '../../types'
9
-
10
- const mockMessage = (id: string): MessageResource =>
11
- ({
12
- id,
13
- type: 'Message',
14
- text: `msg ${id}`,
15
- attachments: [],
16
- deletedAt: null,
17
- replyRootId: null,
18
- }) as MessageResource
19
-
20
- const apiResponse = (data: MessageResource[]): ApiCollection<MessageResource> => ({
21
- data,
22
- links: {},
23
- meta: { count: data.length, totalCount: data.length, next: {} },
24
- })
25
-
26
- const createWrapper = (initialMessageId: string | null) => {
27
- const queryClient = buildTestQueryClient()
28
-
29
- return ({ children }: { children: React.ReactNode }) => (
30
- <QueryClientProvider client={queryClient}>
31
- <Suspense fallback={null}>
32
- <ConversationContextProvider
33
- conversationId={123}
34
- currentPageReplyRootId={null}
35
- initialMessageId={initialMessageId}
36
- initialMessageIdIsAnchor={!!initialMessageId}
37
- >
38
- {children}
39
- </ConversationContextProvider>
40
- </Suspense>
41
- </QueryClientProvider>
42
- )
43
- }
44
-
45
- const flushPromises = async () => {
46
- await act(async () => {
47
- await Promise.resolve()
48
- await Promise.resolve()
49
- await Promise.resolve()
50
- await Promise.resolve()
51
- })
52
- }
53
-
54
- const mockApiClient = (get: jest.Mock) => {
55
- jest.spyOn(useApiClientModule, 'useApiClient').mockReturnValue({
56
- chat: { get },
57
- } as unknown as ReturnType<typeof useApiClientModule.useApiClient>)
58
- }
59
-
60
- describe('useConversationMessages', () => {
61
- afterEach(() => {
62
- jest.restoreAllMocks()
63
- })
64
-
65
- it('fires two parallel seed requests with id_gte/asc and id_lt/desc when anchored', async () => {
66
- const get = jest.fn(({ data }: { data: { where?: Record<string, string> } }) => {
67
- if (data.where?.id_gte === '01B') {
68
- return Promise.resolve(apiResponse([mockMessage('01C'), mockMessage('01B')]))
69
- }
70
- if (data.where?.id_lt === '01B') {
71
- return Promise.resolve(apiResponse([mockMessage('01A')]))
72
- }
73
- return Promise.resolve(apiResponse([]))
74
- })
75
- mockApiClient(get)
76
-
77
- renderHook(() => useConversationMessages({ conversation_id: 123 }), {
78
- wrapper: createWrapper('01B'),
79
- })
80
- await flushPromises()
81
-
82
- expect(get).toHaveBeenCalledTimes(2)
83
- const requested = get.mock.calls.map(
84
- ([req]: [{ data: { where?: Record<string, string>; order?: string } }]) => ({
85
- where: req.data.where,
86
- order: req.data.order,
87
- })
88
- )
89
- expect(requested).toEqual(
90
- expect.arrayContaining([
91
- { where: { id_gte: '01B' }, order: 'asc' },
92
- { where: { id_lt: '01B' }, order: 'desc' },
93
- ])
94
- )
95
- })
96
-
97
- it('fires one fetch with no cursor when not anchored', async () => {
98
- const get = jest.fn(() => Promise.resolve(apiResponse([mockMessage('01A')])))
99
- mockApiClient(get)
100
-
101
- renderHook(() => useConversationMessages({ conversation_id: 123 }), {
102
- wrapper: createWrapper(null),
103
- })
104
- await flushPromises()
105
-
106
- expect(get).toHaveBeenCalledTimes(1)
107
- expect(get.mock.calls[0][0].data.where).toBeUndefined()
108
- })
109
- })
@@ -1,154 +0,0 @@
1
- import { QueryClientProvider } from '@tanstack/react-query'
2
- import { renderHook } from '@testing-library/react-native'
3
- import React, { useEffect } from 'react'
4
- import { buildTestQueryClient } from '../../__utils__/query_client'
5
- import {
6
- ConversationContextProvider,
7
- useConversationContext,
8
- } from '../../contexts/conversation_context'
9
- import * as appStateModule from '../../hooks/use_app_state'
10
- import * as conversationsActionsModule from '../../hooks/use_conversations_actions'
11
- import * as featuresModule from '../../hooks/use_features'
12
- import { useMarkLatestMessageRead } from '../../hooks/use_mark_latest_message_read'
13
- import { ConversationResource } from '../../types'
14
-
15
- const conversation = {
16
- id: 1,
17
- type: 'Conversation',
18
- unreadCount: 3,
19
- unreadReactionCount: 0,
20
- conversationMembership: { lastReadMessageSortKey: '01A' },
21
- } as unknown as ConversationResource
22
-
23
- interface WrapperProps {
24
- replyRootId?: string | null
25
- initialMessageIdIsAnchor?: boolean
26
- atEndOfMessageHistory?: boolean
27
- }
28
-
29
- const createWrapper = ({
30
- replyRootId = null,
31
- initialMessageIdIsAnchor = false,
32
- atEndOfMessageHistory = !initialMessageIdIsAnchor,
33
- }: WrapperProps = {}) => {
34
- const queryClient = buildTestQueryClient()
35
- const Wrapper = ({ children }: { children: React.ReactNode }) => (
36
- <QueryClientProvider client={queryClient}>
37
- <ConversationContextProvider
38
- conversationId={1}
39
- currentPageReplyRootId={replyRootId}
40
- initialMessageId={initialMessageIdIsAnchor ? '01A' : null}
41
- initialMessageIdIsAnchor={initialMessageIdIsAnchor}
42
- >
43
- <AtEndPrimer atEndOfMessageHistory={atEndOfMessageHistory}>{children}</AtEndPrimer>
44
- </ConversationContextProvider>
45
- </QueryClientProvider>
46
- )
47
- return Wrapper
48
- }
49
-
50
- const AtEndPrimer = ({
51
- atEndOfMessageHistory,
52
- children,
53
- }: {
54
- atEndOfMessageHistory: boolean
55
- children: React.ReactNode
56
- }) => {
57
- const { setAtEndOfMessageHistory } = useConversationContext()
58
- useEffect(() => {
59
- setAtEndOfMessageHistory(atEndOfMessageHistory)
60
- }, [atEndOfMessageHistory, setAtEndOfMessageHistory])
61
- return <>{children}</>
62
- }
63
-
64
- const mockFeatures = (enabled: boolean) => {
65
- jest.spyOn(featuresModule, 'useFeatures').mockReturnValue({
66
- features: [],
67
- featureEnabled: () => enabled,
68
- })
69
- }
70
-
71
- const mockMarkRead = () => {
72
- const markRead = jest.fn()
73
- jest.spyOn(conversationsActionsModule, 'useConversationsMarkRead').mockReturnValue({
74
- markRead,
75
- read: false,
76
- } as unknown as ReturnType<typeof conversationsActionsModule.useConversationsMarkRead>)
77
- return markRead
78
- }
79
-
80
- const mockAppState = (state: string = 'active') => {
81
- jest.spyOn(appStateModule, 'useAppState').mockReturnValue(state)
82
- }
83
-
84
- describe('useMarkLatestMessageRead', () => {
85
- afterEach(() => {
86
- jest.restoreAllMocks()
87
- })
88
-
89
- it('fires markRead when JTU is off (backward compat preserved)', () => {
90
- mockAppState()
91
- mockFeatures(false)
92
- const markRead = mockMarkRead()
93
-
94
- renderHook(() => useMarkLatestMessageRead({ conversation }), {
95
- wrapper: createWrapper(),
96
- })
97
-
98
- expect(markRead).toHaveBeenCalledWith(true)
99
- })
100
-
101
- it('does NOT fire markRead when in a reply view (parity fix, ships flag-off)', () => {
102
- mockAppState()
103
- mockFeatures(false)
104
- const markRead = mockMarkRead()
105
-
106
- renderHook(() => useMarkLatestMessageRead({ conversation }), {
107
- wrapper: createWrapper({ replyRootId: 'root-1' }),
108
- })
109
-
110
- expect(markRead).not.toHaveBeenCalled()
111
- })
112
-
113
- it('does NOT fire markRead when JTU is active and user is not at end of history', () => {
114
- mockAppState()
115
- mockFeatures(true)
116
- const markRead = mockMarkRead()
117
-
118
- renderHook(() => useMarkLatestMessageRead({ conversation }), {
119
- wrapper: createWrapper({
120
- initialMessageIdIsAnchor: true,
121
- atEndOfMessageHistory: false,
122
- }),
123
- })
124
-
125
- expect(markRead).not.toHaveBeenCalled()
126
- })
127
-
128
- it('fires markRead when JTU is active and user reaches end of history', () => {
129
- mockAppState()
130
- mockFeatures(true)
131
- const markRead = mockMarkRead()
132
-
133
- renderHook(() => useMarkLatestMessageRead({ conversation }), {
134
- wrapper: createWrapper({
135
- initialMessageIdIsAnchor: true,
136
- atEndOfMessageHistory: true,
137
- }),
138
- })
139
-
140
- expect(markRead).toHaveBeenCalledWith(true)
141
- })
142
-
143
- it('does NOT fire when app state is not active', () => {
144
- mockAppState('background')
145
- mockFeatures(false)
146
- const markRead = mockMarkRead()
147
-
148
- renderHook(() => useMarkLatestMessageRead({ conversation }), {
149
- wrapper: createWrapper(),
150
- })
151
-
152
- expect(markRead).not.toHaveBeenCalled()
153
- })
154
- })