@droppii-org/chat-mobile 0.2.109 → 0.2.110

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 (100) hide show
  1. package/lib/module/screens/chat-detail/ChatDetail.js +42 -7
  2. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  3. package/lib/module/screens/chat-detail/ChatListLegend.js +249 -49
  4. package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
  5. package/lib/module/screens/chat-detail/hooks/createUnreadMarkReadGate.js +32 -0
  6. package/lib/module/screens/chat-detail/hooks/createUnreadMarkReadGate.js.map +1 -0
  7. package/lib/module/screens/chat-detail/hooks/useMarkConversationRead.js +180 -0
  8. package/lib/module/screens/chat-detail/hooks/useMarkConversationRead.js.map +1 -0
  9. package/lib/module/screens/chat-detail/hooks/useUnreadAnchorSeen.js +88 -0
  10. package/lib/module/screens/chat-detail/hooks/useUnreadAnchorSeen.js.map +1 -0
  11. package/lib/module/screens/chat-detail/hooks/useUnreadDeeplinkCheck.js +59 -0
  12. package/lib/module/screens/chat-detail/hooks/useUnreadDeeplinkCheck.js.map +1 -0
  13. package/lib/module/screens/chat-detail/hooks/useUnreadIndicator.js +105 -0
  14. package/lib/module/screens/chat-detail/hooks/useUnreadIndicator.js.map +1 -0
  15. package/lib/module/screens/chat-detail/hooks/useUnreadIndicatorInList.js +157 -0
  16. package/lib/module/screens/chat-detail/hooks/useUnreadIndicatorInList.js.map +1 -0
  17. package/lib/module/screens/chat-detail/legend/LegendChatUnreadButton.js +140 -0
  18. package/lib/module/screens/chat-detail/legend/LegendChatUnreadButton.js.map +1 -0
  19. package/lib/module/screens/chat-detail/legend/LegendChatUnreadDivider.js +51 -0
  20. package/lib/module/screens/chat-detail/legend/LegendChatUnreadDivider.js.map +1 -0
  21. package/lib/module/services/message.js +30 -0
  22. package/lib/module/services/message.js.map +1 -1
  23. package/lib/module/store/message.js +25 -4
  24. package/lib/module/store/message.js.map +1 -1
  25. package/lib/module/store/unreadIndicator.js +26 -0
  26. package/lib/module/store/unreadIndicator.js.map +1 -0
  27. package/lib/module/translation/resources/i18n.js +2 -0
  28. package/lib/module/translation/resources/i18n.js.map +1 -1
  29. package/lib/module/types/chat.js.map +1 -1
  30. package/lib/module/types/unreadIndicator.js +2 -0
  31. package/lib/module/types/unreadIndicator.js.map +1 -0
  32. package/lib/module/utils/markReadPolicy.js +25 -0
  33. package/lib/module/utils/markReadPolicy.js.map +1 -0
  34. package/lib/module/utils/promise.js +16 -0
  35. package/lib/module/utils/promise.js.map +1 -1
  36. package/lib/module/utils/unreadAnchor.js +57 -0
  37. package/lib/module/utils/unreadAnchor.js.map +1 -0
  38. package/lib/module/utils/unreadIndicatorState.js +96 -0
  39. package/lib/module/utils/unreadIndicatorState.js.map +1 -0
  40. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  41. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts +4 -1
  42. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
  43. package/lib/typescript/src/screens/chat-detail/hooks/createUnreadMarkReadGate.d.ts +9 -0
  44. package/lib/typescript/src/screens/chat-detail/hooks/createUnreadMarkReadGate.d.ts.map +1 -0
  45. package/lib/typescript/src/screens/chat-detail/hooks/useMarkConversationRead.d.ts +39 -0
  46. package/lib/typescript/src/screens/chat-detail/hooks/useMarkConversationRead.d.ts.map +1 -0
  47. package/lib/typescript/src/screens/chat-detail/hooks/useUnreadAnchorSeen.d.ts +26 -0
  48. package/lib/typescript/src/screens/chat-detail/hooks/useUnreadAnchorSeen.d.ts.map +1 -0
  49. package/lib/typescript/src/screens/chat-detail/hooks/useUnreadDeeplinkCheck.d.ts +17 -0
  50. package/lib/typescript/src/screens/chat-detail/hooks/useUnreadDeeplinkCheck.d.ts.map +1 -0
  51. package/lib/typescript/src/screens/chat-detail/hooks/useUnreadIndicator.d.ts +21 -0
  52. package/lib/typescript/src/screens/chat-detail/hooks/useUnreadIndicator.d.ts.map +1 -0
  53. package/lib/typescript/src/screens/chat-detail/hooks/useUnreadIndicatorInList.d.ts +35 -0
  54. package/lib/typescript/src/screens/chat-detail/hooks/useUnreadIndicatorInList.d.ts.map +1 -0
  55. package/lib/typescript/src/screens/chat-detail/legend/LegendChatUnreadButton.d.ts +8 -0
  56. package/lib/typescript/src/screens/chat-detail/legend/LegendChatUnreadButton.d.ts.map +1 -0
  57. package/lib/typescript/src/screens/chat-detail/legend/LegendChatUnreadDivider.d.ts +7 -0
  58. package/lib/typescript/src/screens/chat-detail/legend/LegendChatUnreadDivider.d.ts.map +1 -0
  59. package/lib/typescript/src/services/message.d.ts +8 -0
  60. package/lib/typescript/src/services/message.d.ts.map +1 -1
  61. package/lib/typescript/src/store/message.d.ts.map +1 -1
  62. package/lib/typescript/src/store/unreadIndicator.d.ts +10 -0
  63. package/lib/typescript/src/store/unreadIndicator.d.ts.map +1 -0
  64. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  65. package/lib/typescript/src/types/chat.d.ts +3 -0
  66. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  67. package/lib/typescript/src/types/events.d.ts +17 -1
  68. package/lib/typescript/src/types/events.d.ts.map +1 -1
  69. package/lib/typescript/src/types/unreadIndicator.d.ts +45 -0
  70. package/lib/typescript/src/types/unreadIndicator.d.ts.map +1 -0
  71. package/lib/typescript/src/utils/markReadPolicy.d.ts +27 -0
  72. package/lib/typescript/src/utils/markReadPolicy.d.ts.map +1 -0
  73. package/lib/typescript/src/utils/promise.d.ts +6 -0
  74. package/lib/typescript/src/utils/promise.d.ts.map +1 -1
  75. package/lib/typescript/src/utils/unreadAnchor.d.ts +33 -0
  76. package/lib/typescript/src/utils/unreadAnchor.d.ts.map +1 -0
  77. package/lib/typescript/src/utils/unreadIndicatorState.d.ts +26 -0
  78. package/lib/typescript/src/utils/unreadIndicatorState.d.ts.map +1 -0
  79. package/package.json +2 -2
  80. package/src/screens/chat-detail/ChatDetail.tsx +58 -5
  81. package/src/screens/chat-detail/ChatListLegend.tsx +314 -46
  82. package/src/screens/chat-detail/hooks/createUnreadMarkReadGate.ts +41 -0
  83. package/src/screens/chat-detail/hooks/useMarkConversationRead.ts +197 -0
  84. package/src/screens/chat-detail/hooks/useUnreadAnchorSeen.ts +105 -0
  85. package/src/screens/chat-detail/hooks/useUnreadDeeplinkCheck.ts +68 -0
  86. package/src/screens/chat-detail/hooks/useUnreadIndicator.ts +112 -0
  87. package/src/screens/chat-detail/hooks/useUnreadIndicatorInList.ts +181 -0
  88. package/src/screens/chat-detail/legend/LegendChatUnreadButton.tsx +165 -0
  89. package/src/screens/chat-detail/legend/LegendChatUnreadDivider.tsx +52 -0
  90. package/src/services/message.ts +42 -0
  91. package/src/store/message.ts +23 -5
  92. package/src/store/unreadIndicator.ts +38 -0
  93. package/src/translation/resources/i18n.ts +2 -0
  94. package/src/types/chat.ts +3 -0
  95. package/src/types/events.ts +18 -1
  96. package/src/types/unreadIndicator.ts +49 -0
  97. package/src/utils/markReadPolicy.ts +55 -0
  98. package/src/utils/promise.ts +20 -0
  99. package/src/utils/unreadAnchor.ts +87 -0
  100. package/src/utils/unreadIndicatorState.ts +131 -0
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+
3
+ import { useCallback, useEffect, useRef, useState } from 'react';
4
+ import { useIsFocused, useNavigation, useRoute } from '@react-navigation/native';
5
+ import { ChatMessageAPI } from "../../../services/message.js";
6
+ import { useUnreadIndicatorStore } from "../../../store/unreadIndicator.js";
7
+ import { toUnreadAnchor } from "../../../utils/unreadAnchor.js";
8
+ import { useUnreadDeeplinkCheck } from "./useUnreadDeeplinkCheck.js";
9
+
10
+ // A blur within this long of a library-initiated child navigation (Thread
11
+ // Info, in-thread search, add member) keeps the indicator; any other blur
12
+ // (tab switch, another module) clears it.
13
+ export const CHILD_NAV_TTL_MS = 1000;
14
+
15
+ /**
16
+ * Owns this screen's unread-indicator session. `getFirstUnreadMessage` records
17
+ * the thread's open time server-side, so it's called exactly once per route
18
+ * visit: on the first focus after access is confirmed. A remount of ChatDetail
19
+ * inside the same route (access re-check, SDK reconnect) adopts the existing
20
+ * session instead of calling again; a new route key is a new visit.
21
+ *
22
+ * Must be called above `useMessages` (see `useUnreadDeeplinkCheck`).
23
+ */
24
+ export const useUnreadIndicator = ({
25
+ conversationId,
26
+ isBotConversation,
27
+ targetMessageId,
28
+ accessConfirmed
29
+ }) => {
30
+ const routeKey = useRoute().key;
31
+ const navigation = useNavigation();
32
+ const isFocused = useIsFocused();
33
+ // Ref for listeners and async code; state so token-scoped selectors re-run
34
+ // when it's set (adopting doesn't change the store).
35
+ const tokenRef = useRef(null);
36
+ const [unreadToken, setUnreadToken] = useState(null);
37
+ const childNavAtRef = useRef(0);
38
+ useEffect(() => {
39
+ if (!conversationId) return;
40
+ const s = useUnreadIndicatorStore.getState();
41
+ if (s.routeKey === routeKey && s.conversationId === conversationId) {
42
+ tokenRef.current = s.token;
43
+ setUnreadToken(s.token);
44
+ return;
45
+ }
46
+ if (!isFocused || !accessConfirmed) return;
47
+ const token = s.begin({
48
+ routeKey,
49
+ conversationId,
50
+ skip: isBotConversation,
51
+ awaitDeeplinkCheck: !!targetMessageId
52
+ });
53
+ tokenRef.current = token;
54
+ setUnreadToken(token);
55
+ if (isBotConversation) return;
56
+ let resolved = false;
57
+ (async () => {
58
+ try {
59
+ const message = await ChatMessageAPI.getFirstUnreadMessage(conversationId);
60
+ useUnreadIndicatorStore.getState().resolve(token, toUnreadAnchor(message, conversationId));
61
+ resolved = true;
62
+ } catch (error) {
63
+ if (__DEV__) console.log('[unread] resolve failed', error);
64
+ } finally {
65
+ // The mark-read gate must always open.
66
+ if (!resolved) useUnreadIndicatorStore.getState().resolve(token, null);
67
+ }
68
+ })();
69
+ // targetMessageId is deliberately not a dependency: a target change on
70
+ // the same screen re-arms D11, it doesn't start a new visit.
71
+ // eslint-disable-next-line react-hooks/exhaustive-deps
72
+ }, [conversationId, routeKey, isFocused, accessConfirmed, isBotConversation]);
73
+ const markChildNavigation = useCallback(() => {
74
+ childNavAtRef.current = Date.now();
75
+ }, []);
76
+ useEffect(() => {
77
+ const offBlur = navigation.addListener('blur', () => {
78
+ const isChildNavigation = Date.now() - childNavAtRef.current < CHILD_NAV_TTL_MS;
79
+ childNavAtRef.current = 0;
80
+ const token = tokenRef.current;
81
+ // Token-scoped: a no-op if another visit has superseded this session.
82
+ if (!isChildNavigation && token != null) {
83
+ useUnreadIndicatorStore.getState().clear(token);
84
+ }
85
+ });
86
+ const offFocus = navigation.addListener('focus', () => {
87
+ childNavAtRef.current = 0;
88
+ });
89
+ return () => {
90
+ offBlur();
91
+ offFocus();
92
+ };
93
+ }, [navigation]);
94
+ useUnreadDeeplinkCheck({
95
+ targetMessageId,
96
+ unreadToken,
97
+ tokenRef
98
+ });
99
+ return {
100
+ unreadToken,
101
+ unreadTokenRef: tokenRef,
102
+ markChildNavigation
103
+ };
104
+ };
105
+ //# sourceMappingURL=useUnreadIndicator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["useCallback","useEffect","useRef","useState","useIsFocused","useNavigation","useRoute","ChatMessageAPI","useUnreadIndicatorStore","toUnreadAnchor","useUnreadDeeplinkCheck","CHILD_NAV_TTL_MS","useUnreadIndicator","conversationId","isBotConversation","targetMessageId","accessConfirmed","routeKey","key","navigation","isFocused","tokenRef","unreadToken","setUnreadToken","childNavAtRef","s","getState","current","token","begin","skip","awaitDeeplinkCheck","resolved","message","getFirstUnreadMessage","resolve","error","__DEV__","console","log","markChildNavigation","Date","now","offBlur","addListener","isChildNavigation","clear","offFocus","unreadTokenRef"],"sourceRoot":"../../../../../src","sources":["screens/chat-detail/hooks/useUnreadIndicator.ts"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+
3
+ import { useCallback, useEffect, useMemo, useRef } from 'react';
4
+ import { DeviceEventEmitter } from 'react-native';
5
+ import { useUnreadIndicatorStore } from "../../../store/unreadIndicator.js";
6
+ import { useMessageStore } from "../../../store/message.js";
7
+ import { ChatEvent } from "../../../types/events.js";
8
+ import { resolveUnreadAnchorRowId } from "../../../utils/unreadAnchor.js";
9
+ import { useUnreadAnchorSeen } from "./useUnreadAnchorSeen.js";
10
+
11
+ /** Lands the divider ~100px below the list top, no highlight, never exits. */
12
+ export const UNREAD_JUMP_OPTIONS = {
13
+ highlight: false,
14
+ viewPosition: 0,
15
+ viewOffset: 100,
16
+ exitOnUnavailable: false
17
+ };
18
+ // Parity with searchMessages' own re-emit delay.
19
+ const POST_SEARCH_SCROLL_DELAY_MS = 300;
20
+ // Anchor not reachable by id (not in the local DB): page older history at
21
+ // most this many times (~75 messages) looking for the boundary.
22
+ const MAX_ANCHOR_PAGES = 5;
23
+ /**
24
+ * Wires the unread session into the list: which row carries the divider,
25
+ * whether the pill shows, and the pill's jump. The session is token-scoped —
26
+ * a second ChatDetail of the same thread never reads or dismisses this one's.
27
+ */
28
+ export const useUnreadIndicatorInList = ({
29
+ messages,
30
+ unreadToken,
31
+ isLoading,
32
+ hasMoreOld,
33
+ moreOldLoading,
34
+ onLoadMoreOld,
35
+ isBotConversation,
36
+ listRef,
37
+ jumpInFlightRef,
38
+ setJumpInFlight
39
+ }) => {
40
+ const own = useUnreadIndicatorStore(s => unreadToken != null && s.token === unreadToken);
41
+ const anchor = useUnreadIndicatorStore(s => s.anchor);
42
+ const pillDismissed = useUnreadIndicatorStore(s => s.pillDismissed);
43
+ const awaiting = useUnreadIndicatorStore(s => s.awaitingDeeplinkCheck);
44
+ // Raw window (before rendering filters) decides whether the boundary is in.
45
+ const oldestLoadedSeq = useMessageStore(s => s.messages[0]?.seq ?? 0);
46
+ const active = own && !isBotConversation && !awaiting && !!anchor;
47
+ const unreadAnchorRowId = useMemo(() => active ? resolveUnreadAnchorRowId(messages, anchor, {
48
+ oldestLoadedSeq,
49
+ hasMoreOld
50
+ }) : null, [active, messages, anchor, oldestLoadedSeq, hasMoreOld]);
51
+ const seen = useUnreadAnchorSeen({
52
+ rowId: unreadAnchorRowId,
53
+ unreadToken,
54
+ isLoading,
55
+ listRef
56
+ });
57
+ const latest = useRef({
58
+ unreadAnchorRowId,
59
+ unreadToken,
60
+ hasMoreOld
61
+ });
62
+ latest.current = {
63
+ unreadAnchorRowId,
64
+ unreadToken,
65
+ hasMoreOld
66
+ };
67
+ const onLoadMoreOldRef = useRef(onLoadMoreOld);
68
+ onLoadMoreOldRef.current = onLoadMoreOld;
69
+ const pendingJumpRef = useRef('idle');
70
+ const pagesRef = useRef(0);
71
+
72
+ // After the window has landed (or been kept): land on the row re-resolved
73
+ // by seq, page older history toward it, or give up silently (no toast).
74
+ const landOrPage = useCallback(() => {
75
+ const {
76
+ unreadAnchorRowId: id,
77
+ hasMoreOld: more
78
+ } = latest.current;
79
+ if (id) {
80
+ pendingJumpRef.current = 'idle';
81
+ DeviceEventEmitter.emit(ChatEvent.ScrollToMessage, {
82
+ ...UNREAD_JUMP_OPTIONS,
83
+ clientMessageId: id,
84
+ fromSearch: true
85
+ });
86
+ return;
87
+ }
88
+ if (more && pagesRef.current < MAX_ANCHOR_PAGES && onLoadMoreOldRef.current) {
89
+ pagesRef.current += 1;
90
+ pendingJumpRef.current = 'paging';
91
+ onLoadMoreOldRef.current(true);
92
+ return;
93
+ }
94
+ pendingJumpRef.current = 'idle';
95
+ setJumpInFlight(false);
96
+ }, [setJumpInFlight]);
97
+ useEffect(() => {
98
+ if (isLoading) {
99
+ if (pendingJumpRef.current === 'requested') {
100
+ pendingJumpRef.current = 'loading';
101
+ }
102
+ return;
103
+ }
104
+ if (pendingJumpRef.current !== 'loading') return;
105
+ const timer = setTimeout(landOrPage, POST_SEARCH_SCROLL_DELAY_MS);
106
+ return () => clearTimeout(timer);
107
+ }, [isLoading, landOrPage]);
108
+
109
+ // One older page landed while paging toward the anchor.
110
+ const prevMoreOldLoadingRef = useRef(moreOldLoading);
111
+ useEffect(() => {
112
+ const wasLoading = prevMoreOldLoadingRef.current;
113
+ prevMoreOldLoadingRef.current = moreOldLoading;
114
+ if (wasLoading && !moreOldLoading && pendingJumpRef.current === 'paging') {
115
+ landOrPage();
116
+ }
117
+ }, [moreOldLoading, landOrPage]);
118
+ const jumpToFirstUnread = useCallback(() => {
119
+ const s = useUnreadIndicatorStore.getState();
120
+ const {
121
+ unreadToken: token,
122
+ unreadAnchorRowId: id
123
+ } = latest.current;
124
+ if (token == null || s.token !== token || !s.anchor) return;
125
+ if (s.pillDismissed || jumpInFlightRef.current) return;
126
+ s.dismissPill(token);
127
+ setJumpInFlight(true);
128
+ if (id) {
129
+ DeviceEventEmitter.emit(ChatEvent.ScrollToMessage, {
130
+ ...UNREAD_JUMP_OPTIONS,
131
+ clientMessageId: id
132
+ });
133
+ return;
134
+ }
135
+ pendingJumpRef.current = 'requested';
136
+ pagesRef.current = 0;
137
+ DeviceEventEmitter.emit(ChatEvent.ScrollToMessage, {
138
+ ...UNREAD_JUMP_OPTIONS,
139
+ clientMessageId: s.anchor.clientMsgID,
140
+ reemitAfterSearch: false
141
+ });
142
+ }, [jumpInFlightRef, setJumpInFlight]);
143
+
144
+ // Evaluated in render, including "the divider is on screen right now", so a
145
+ // late anchor never flashes the pill for a frame.
146
+ const isPillVisible = active && !pillDismissed && !isLoading && seen.positionSettled && seen.viewabilityReady && !(unreadAnchorRowId && seen.isAnchorTopVisibleNow());
147
+ return {
148
+ unreadAnchorRowId,
149
+ isPillVisible,
150
+ viewabilityConfig: seen.viewabilityConfig,
151
+ handleViewableItemsChanged: seen.handleViewableItemsChanged,
152
+ checkAnchorSeen: seen.checkAnchorSeen,
153
+ markPositionSettled: seen.markPositionSettled,
154
+ jumpToFirstUnread
155
+ };
156
+ };
157
+ //# sourceMappingURL=useUnreadIndicatorInList.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["useCallback","useEffect","useMemo","useRef","DeviceEventEmitter","useUnreadIndicatorStore","useMessageStore","ChatEvent","resolveUnreadAnchorRowId","useUnreadAnchorSeen","UNREAD_JUMP_OPTIONS","highlight","viewPosition","viewOffset","exitOnUnavailable","POST_SEARCH_SCROLL_DELAY_MS","MAX_ANCHOR_PAGES","useUnreadIndicatorInList","messages","unreadToken","isLoading","hasMoreOld","moreOldLoading","onLoadMoreOld","isBotConversation","listRef","jumpInFlightRef","setJumpInFlight","own","s","token","anchor","pillDismissed","awaiting","awaitingDeeplinkCheck","oldestLoadedSeq","seq","active","unreadAnchorRowId","seen","rowId","latest","current","onLoadMoreOldRef","pendingJumpRef","pagesRef","landOrPage","id","more","emit","ScrollToMessage","clientMessageId","fromSearch","timer","setTimeout","clearTimeout","prevMoreOldLoadingRef","wasLoading","jumpToFirstUnread","getState","dismissPill","clientMsgID","reemitAfterSearch","isPillVisible","positionSettled","viewabilityReady","isAnchorTopVisibleNow","viewabilityConfig","handleViewableItemsChanged","checkAnchorSeen","markPositionSettled"],"sourceRoot":"../../../../../src","sources":["screens/chat-detail/hooks/useUnreadIndicatorInList.ts"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+
3
+ import { memo, useEffect } from 'react';
4
+ import { StyleSheet } from 'react-native';
5
+ import Animated, { Easing, cancelAnimation, useAnimatedStyle, useReducedMotion, useSharedValue, withDelay, withRepeat, withSequence, withTiming } from 'react-native-reanimated';
6
+ import { KColors, KContainer, KImage, KLabel, KRadiusValue } from '@droppii/libs';
7
+ import { trans } from "../../../translation/index.js";
8
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
+ const FADE = {
10
+ duration: 200,
11
+ easing: Easing.out(Easing.ease)
12
+ };
13
+
14
+ // Chevron cascade (D5): the bottom chevron leads and the top one follows, an
15
+ // upward motion cue. Designer may tune these.
16
+ const CYCLE_MS = 1200;
17
+ const STEP_MS = CYCLE_MS / 4;
18
+ const DIM = 0.35;
19
+ const CHEVRON_SIZE = 14;
20
+
21
+ // The touch target is real padded bounds, not `hitSlop`: a hitSlop reaching
22
+ // outside the parent Animated.View is dead on Android. 23 + 2 * 11 = 45pt.
23
+ const TOUCH_PAD_V = 11;
24
+ const TOUCH_PAD_L = 8;
25
+ // The pill itself sits 16 below the list top.
26
+ const PILL_TOP = 16;
27
+ const wave = () => withRepeat(withSequence(withTiming(DIM, {
28
+ duration: STEP_MS
29
+ }), withTiming(1, {
30
+ duration: STEP_MS
31
+ })), -1);
32
+ const UnreadChevrons = /*#__PURE__*/memo(() => {
33
+ const reducedMotion = useReducedMotion();
34
+ const top = useSharedValue(1);
35
+ const bottom = useSharedValue(1);
36
+ useEffect(() => {
37
+ if (reducedMotion) return;
38
+ bottom.value = wave();
39
+ top.value = withDelay(STEP_MS, wave());
40
+ return () => {
41
+ cancelAnimation(top);
42
+ cancelAnimation(bottom);
43
+ };
44
+ }, [reducedMotion, top, bottom]);
45
+ const topStyle = useAnimatedStyle(() => ({
46
+ opacity: top.value
47
+ }));
48
+ const bottomStyle = useAnimatedStyle(() => ({
49
+ opacity: bottom.value
50
+ }));
51
+ return /*#__PURE__*/_jsxs(KContainer.View, {
52
+ style: styles.chevrons,
53
+ children: [/*#__PURE__*/_jsx(Animated.View, {
54
+ style: topStyle,
55
+ children: /*#__PURE__*/_jsx(KImage.VectorIcons, {
56
+ name: "angle-up-o",
57
+ size: CHEVRON_SIZE,
58
+ color: KColors.palette.gray.w900
59
+ })
60
+ }), /*#__PURE__*/_jsx(Animated.View, {
61
+ style: [styles.chevronOverlap, bottomStyle],
62
+ children: /*#__PURE__*/_jsx(KImage.VectorIcons, {
63
+ name: "angle-up-o",
64
+ size: CHEVRON_SIZE,
65
+ color: KColors.palette.gray.w900
66
+ })
67
+ })]
68
+ });
69
+ });
70
+ UnreadChevrons.displayName = 'UnreadChevrons';
71
+ /** "Tin chưa đọc" half-pill, top-right of the list; jumps to the divider. */
72
+ export const LegendChatUnreadButton = /*#__PURE__*/memo(({
73
+ visible,
74
+ onPress
75
+ }) => {
76
+ const opacity = useSharedValue(0);
77
+ useEffect(() => {
78
+ opacity.value = withTiming(visible ? 1 : 0, FADE);
79
+ }, [visible, opacity]);
80
+ const animatedStyle = useAnimatedStyle(() => ({
81
+ opacity: opacity.value
82
+ }));
83
+ return /*#__PURE__*/_jsx(Animated.View, {
84
+ style: [styles.anchor, animatedStyle]
85
+ // From the prop, not the animated opacity: deterministic on Old Arch.
86
+ ,
87
+ pointerEvents: visible ? 'box-none' : 'none',
88
+ children: /*#__PURE__*/_jsx(KContainer.Touchable, {
89
+ onPress: onPress,
90
+ activeOpacity: 0.7,
91
+ style: styles.touchArea,
92
+ accessible: true,
93
+ accessibilityRole: "button",
94
+ accessibilityLabel: trans('chat:unread_jump_button'),
95
+ children: /*#__PURE__*/_jsxs(KContainer.View, {
96
+ style: styles.pill,
97
+ children: [visible && /*#__PURE__*/_jsx(UnreadChevrons, {}), /*#__PURE__*/_jsx(KLabel.Text, {
98
+ typo: "TextXsMedium",
99
+ color: KColors.palette.gray.w900,
100
+ children: trans('chat:unread_jump_button')
101
+ })]
102
+ })
103
+ })
104
+ });
105
+ });
106
+ LegendChatUnreadButton.displayName = 'LegendChatUnreadButton';
107
+ const styles = StyleSheet.create({
108
+ anchor: {
109
+ position: 'absolute',
110
+ top: PILL_TOP - TOUCH_PAD_V,
111
+ right: 0,
112
+ zIndex: 10
113
+ },
114
+ touchArea: {
115
+ paddingVertical: TOUCH_PAD_V,
116
+ paddingLeft: TOUCH_PAD_L
117
+ },
118
+ pill: {
119
+ flexDirection: 'row',
120
+ alignItems: 'center',
121
+ gap: 2,
122
+ paddingHorizontal: 6,
123
+ paddingVertical: 2,
124
+ backgroundColor: KColors.palette.grayCool.w25,
125
+ borderWidth: 1,
126
+ borderRightWidth: 0,
127
+ borderColor: KColors.white,
128
+ borderTopLeftRadius: KRadiusValue.round,
129
+ borderBottomLeftRadius: KRadiusValue.round
130
+ },
131
+ chevrons: {
132
+ width: CHEVRON_SIZE,
133
+ height: CHEVRON_SIZE,
134
+ justifyContent: 'center'
135
+ },
136
+ chevronOverlap: {
137
+ marginTop: -8
138
+ }
139
+ });
140
+ //# sourceMappingURL=LegendChatUnreadButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["memo","useEffect","StyleSheet","Animated","Easing","cancelAnimation","useAnimatedStyle","useReducedMotion","useSharedValue","withDelay","withRepeat","withSequence","withTiming","KColors","KContainer","KImage","KLabel","KRadiusValue","trans","jsx","_jsx","jsxs","_jsxs","FADE","duration","easing","out","ease","CYCLE_MS","STEP_MS","DIM","CHEVRON_SIZE","TOUCH_PAD_V","TOUCH_PAD_L","PILL_TOP","wave","UnreadChevrons","reducedMotion","top","bottom","value","topStyle","opacity","bottomStyle","View","style","styles","chevrons","children","VectorIcons","name","size","color","palette","gray","w900","chevronOverlap","displayName","LegendChatUnreadButton","visible","onPress","animatedStyle","anchor","pointerEvents","Touchable","activeOpacity","touchArea","accessible","accessibilityRole","accessibilityLabel","pill","Text","typo","create","position","right","zIndex","paddingVertical","paddingLeft","flexDirection","alignItems","gap","paddingHorizontal","backgroundColor","grayCool","w25","borderWidth","borderRightWidth","borderColor","white","borderTopLeftRadius","round","borderBottomLeftRadius","width","height","justifyContent","marginTop"],"sourceRoot":"../../../../../src","sources":["screens/chat-detail/legend/LegendChatUnreadButton.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+
3
+ import { memo } from 'react';
4
+ import { StyleSheet } from 'react-native';
5
+ import { KColors, KContainer, KDivider, KLabel, KSpacingValue } from '@droppii/libs';
6
+ import { trans } from "../../../translation/index.js";
7
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
+ const LINE_COLOR = KColors.opacity.grayDark[10];
9
+
10
+ /**
11
+ * "TIN NHẮN CHƯA ĐỌC" row above the first unread message. Figma spacing: the
12
+ * previous row's 8 bottom margin + 8 top margin + 4 padding = 20 above the
13
+ * lines; 4 padding + 12 bottom margin + the time label's 8 = 24 below.
14
+ */
15
+ export const LegendChatUnreadDivider = /*#__PURE__*/memo(() => /*#__PURE__*/_jsxs(KContainer.View, {
16
+ row: true,
17
+ center: true,
18
+ paddingH: "1rem",
19
+ paddingV: "0.25rem",
20
+ style: styles.root,
21
+ accessible: true,
22
+ accessibilityLabel: trans('chat:unread_divider'),
23
+ children: [/*#__PURE__*/_jsx(KContainer.View, {
24
+ flex: true,
25
+ children: /*#__PURE__*/_jsx(KDivider, {
26
+ type: "line",
27
+ size: "hairline",
28
+ background: LINE_COLOR
29
+ })
30
+ }), /*#__PURE__*/_jsx(KLabel.Text, {
31
+ typo: "TextXsBold",
32
+ color: KColors.palette.gray.w200,
33
+ marginH: "1rem",
34
+ children: trans('chat:unread_divider')
35
+ }), /*#__PURE__*/_jsx(KContainer.View, {
36
+ flex: true,
37
+ children: /*#__PURE__*/_jsx(KDivider, {
38
+ type: "line",
39
+ size: "hairline",
40
+ background: LINE_COLOR
41
+ })
42
+ })]
43
+ }));
44
+ LegendChatUnreadDivider.displayName = 'LegendChatUnreadDivider';
45
+ const styles = StyleSheet.create({
46
+ root: {
47
+ marginTop: KSpacingValue['0.5rem'],
48
+ marginBottom: KSpacingValue['0.75rem']
49
+ }
50
+ });
51
+ //# sourceMappingURL=LegendChatUnreadDivider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["memo","StyleSheet","KColors","KContainer","KDivider","KLabel","KSpacingValue","trans","jsx","_jsx","jsxs","_jsxs","LINE_COLOR","opacity","grayDark","LegendChatUnreadDivider","View","row","center","paddingH","paddingV","style","styles","root","accessible","accessibilityLabel","children","flex","type","size","background","Text","typo","color","palette","gray","w200","marginH","displayName","create","marginTop","marginBottom"],"sourceRoot":"../../../../../src","sources":["screens/chat-detail/legend/LegendChatUnreadDivider.tsx"],"mappings":";;AAAA,SAASA,IAAI,QAAQ,OAAO;AAC5B,SAASC,UAAU,QAAQ,cAAc;AACzC,SACEC,OAAO,EACPC,UAAU,EACVC,QAAQ,EACRC,MAAM,EACNC,aAAa,QACR,eAAe;AACtB,SAASC,KAAK,QAAQ,+BAAsB;AAAC,SAAAC,GAAA,IAAAC,IAAA,EAAAC,IAAA,IAAAC,KAAA;AAE7C,MAAMC,UAAU,GAAGV,OAAO,CAACW,OAAO,CAACC,QAAQ,CAAC,EAAE,CAAC;;AAE/C;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,uBAAuB,gBAAGf,IAAI,CAAC,mBAC1CW,KAAA,CAACR,UAAU,CAACa,IAAI;EACdC,GAAG;EACHC,MAAM;EACNC,QAAQ,EAAC,MAAM;EACfC,QAAQ,EAAC,SAAS;EAClBC,KAAK,EAAEC,MAAM,CAACC,IAAK;EACnBC,UAAU;EACVC,kBAAkB,EAAElB,KAAK,CAAC,qBAAqB,CAAE;EAAAmB,QAAA,gBAEjDjB,IAAA,CAACN,UAAU,CAACa,IAAI;IAACW,IAAI;IAAAD,QAAA,eACnBjB,IAAA,CAACL,QAAQ;MAACwB,IAAI,EAAC,MAAM;MAACC,IAAI,EAAC,UAAU;MAACC,UAAU,EAAElB;IAAW,CAAE;EAAC,CACjD,CAAC,eAClBH,IAAA,CAACJ,MAAM,CAAC0B,IAAI;IACVC,IAAI,EAAC,YAAY;IACjBC,KAAK,EAAE/B,OAAO,CAACgC,OAAO,CAACC,IAAI,CAACC,IAAK;IACjCC,OAAO,EAAC,MAAM;IAAAX,QAAA,EAEbnB,KAAK,CAAC,qBAAqB;EAAC,CAClB,CAAC,eACdE,IAAA,CAACN,UAAU,CAACa,IAAI;IAACW,IAAI;IAAAD,QAAA,eACnBjB,IAAA,CAACL,QAAQ;MAACwB,IAAI,EAAC,MAAM;MAACC,IAAI,EAAC,UAAU;MAACC,UAAU,EAAElB;IAAW,CAAE;EAAC,CACjD,CAAC;AAAA,CACH,CAClB,CAAC;AAEFG,uBAAuB,CAACuB,WAAW,GAAG,yBAAyB;AAE/D,MAAMhB,MAAM,GAAGrB,UAAU,CAACsC,MAAM,CAAC;EAC/BhB,IAAI,EAAE;IACJiB,SAAS,EAAElC,aAAa,CAAC,QAAQ,CAAC;IAClCmC,YAAY,EAAEnC,aAAa,CAAC,SAAS;EACvC;AACF,CAAC,CAAC","ignoreList":[]}
@@ -3,7 +3,14 @@
3
3
  import OpenIMSDK, { ViewType } from '@droppii/openim-rn-client-sdk';
4
4
  import { useConversationStore } from "../store/index.js";
5
5
  import { getReceiverId } from "../utils/message.js";
6
+ import { perfMark } from "../utils/perf-marker.js";
7
+ import { withTimeout } from "../utils/promise.js";
6
8
  const DEFAULT_PAGE_SIZE = 20;
9
+
10
+ // Rendering never waits on this, only the read receipt does, while a timeout
11
+ // loses the indicator for good. Tune from the measured p95 of
12
+ // `unread_anchor_response.ms` before release.
13
+ export const FIRST_UNREAD_TIMEOUT_MS = 5000;
7
14
  export let ChatMessageAPI;
8
15
  (function (_ChatMessageAPI) {
9
16
  async function fetchHistoryMessages(conversationID, options) {
@@ -45,6 +52,29 @@ export let ChatMessageAPI;
45
52
  return Promise.all([OpenIMSDK.markConversationMessageAsRead(conversationID), OpenIMSDK.resetConversationGroupAtType(conversationID)]);
46
53
  }
47
54
  _ChatMessageAPI.markConversationAsRead = markConversationAsRead;
55
+ async function getFirstUnreadMessage(conversationID, timeoutMs = FIRST_UNREAD_TIMEOUT_MS) {
56
+ const startedAt = Date.now();
57
+ try {
58
+ perfMark('unread_anchor_request');
59
+ const res = await withTimeout(OpenIMSDK.getFirstUnreadMessage(conversationID), timeoutMs);
60
+ perfMark('unread_anchor_response', {
61
+ found: !!res?.message,
62
+ ms: Date.now() - startedAt
63
+ });
64
+ return res?.message ?? null;
65
+ } catch (error) {
66
+ try {
67
+ perfMark('unread_anchor_response', {
68
+ found: false,
69
+ failed: true,
70
+ ms: Date.now() - startedAt
71
+ });
72
+ } catch {}
73
+ if (__DEV__) console.log('[unread] getFirstUnreadMessage failed', error);
74
+ return null;
75
+ }
76
+ }
77
+ _ChatMessageAPI.getFirstUnreadMessage = getFirstUnreadMessage;
48
78
  async function getCurrentUserId() {
49
79
  return OpenIMSDK.getLoginUserID();
50
80
  }
@@ -1 +1 @@
1
- {"version":3,"names":["OpenIMSDK","ViewType","useConversationStore","getReceiverId","DEFAULT_PAGE_SIZE","ChatMessageAPI","_ChatMessageAPI","fetchHistoryMessages","conversationID","options","getAdvancedHistoryMessageListApp","count","startClientMsgID","viewType","History","applicationType","sendTextMessage","params","currentConversation","getState","getCurrentConversation","message","createTextMessage","text","sentMessage","sendMessage","searchLocalMessages","keywordList","messageTypeList","pageIndex","markConversationAsRead","Promise","all","markConversationMessageAsRead","resetConversationGroupAtType","getCurrentUserId","getLoginUserID"],"sourceRoot":"../../../src","sources":["services/message.ts"],"mappings":";;AAAA,OAAOA,SAAS,IACdC,QAAQ,QAGH,+BAA+B;AACtC,SAASC,oBAAoB,QAAQ,mBAAU;AAC/C,SAASC,aAAa,QAAQ,qBAAkB;AAEhD,MAAMC,iBAAiB,GAAG,EAAE;AAAC,WAMZC,cAAc;AAAA,WAAAC,eAAA;EACtB,eAAeC,oBAAoBA,CACxCC,cAAsB,EACtBC,OAKC,EAC8B;IAC/B,OAAOT,SAAS,CAACU,gCAAgC,CAAC;MAChDF,cAAc;MACdG,KAAK,EAAEF,OAAO,EAAEE,KAAK,IAAIP,iBAAiB;MAC1CQ,gBAAgB,EAAEH,OAAO,EAAEG,gBAAgB,IAAI,EAAE;MACjDC,QAAQ,EAAEZ,QAAQ,CAACa,OAAO;MAC1BC,eAAe,EAAEN,OAAO,EAAEM,eAAe,IAAI;IAC/C,CAAC,CAAC;EACJ;EAACT,eAAA,CAAAC,oBAAA,GAAAA,oBAAA;EAEM,eAAeS,eAAeA,CAACC,MAErC,EAAwB;IACvB,MAAMC,mBAAmB,GAAGhB,oBAAoB,CAC7CiB,QAAQ,CAAC,CAAC,CACVC,sBAAsB,CAAC,CAAC;IAE3B,MAAMC,OAAO,GAAG,MAAMrB,SAAS,CAACsB,iBAAiB,CAACL,MAAM,CAACM,IAAI,CAAC;IAE9D,MAAMC,WAAW,GAAG,MAAMxB,SAAS,CAACyB,WAAW,CAAC;MAC9C,GAAGtB,aAAa,CAACe,mBAAmB,CAAC;MACrCG;IACF,CAAC,CAAC;IAEF,OAAOG,WAAW;EACpB;EAAClB,eAAA,CAAAU,eAAA,GAAAA,eAAA;EAEM,eAAeU,mBAAmBA,CACvClB,cAAsB,EACtBC,OAKC,EACD;IACA,OAAOT,SAAS,CAAC0B,mBAAmB,CAAC;MACnClB,cAAc;MACdmB,WAAW,EAAElB,OAAO,EAAEkB,WAAW,IAAI,CAAC,GAAG,CAAC;MAC1CC,eAAe,EAAEnB,OAAO,EAAEmB,eAAe;MACzCC,SAAS,EAAEpB,OAAO,EAAEoB,SAAS,IAAI,CAAC;MAClClB,KAAK,EAAEF,OAAO,EAAEE,KAAK,IAAI;IAC3B,CAAC,CAAC;EACJ;EAACL,eAAA,CAAAoB,mBAAA,GAAAA,mBAAA;EAEM,eAAeI,sBAAsBA,CAACtB,cAAsB,EAAE;IACnE;IACA;IACA;IACA;IACA;IACA,OAAOuB,OAAO,CAACC,GAAG,CAAC,CACjBhC,SAAS,CAACiC,6BAA6B,CAACzB,cAAc,CAAC,EACvDR,SAAS,CAACkC,4BAA4B,CAAC1B,cAAc,CAAC,CACvD,CAAC;EACJ;EAACF,eAAA,CAAAwB,sBAAA,GAAAA,sBAAA;EAEM,eAAeK,gBAAgBA,CAAA,EAAG;IACvC,OAAOnC,SAAS,CAACoC,cAAc,CAAC,CAAC;EACnC;EAAC9B,eAAA,CAAA6B,gBAAA,GAAAA,gBAAA;AAAA,GApEc9B,cAAc,KAAdA,cAAc","ignoreList":[]}
1
+ {"version":3,"names":["OpenIMSDK","ViewType","useConversationStore","getReceiverId","perfMark","withTimeout","DEFAULT_PAGE_SIZE","FIRST_UNREAD_TIMEOUT_MS","ChatMessageAPI","_ChatMessageAPI","fetchHistoryMessages","conversationID","options","getAdvancedHistoryMessageListApp","count","startClientMsgID","viewType","History","applicationType","sendTextMessage","params","currentConversation","getState","getCurrentConversation","message","createTextMessage","text","sentMessage","sendMessage","searchLocalMessages","keywordList","messageTypeList","pageIndex","markConversationAsRead","Promise","all","markConversationMessageAsRead","resetConversationGroupAtType","getFirstUnreadMessage","timeoutMs","startedAt","Date","now","res","found","ms","error","failed","__DEV__","console","log","getCurrentUserId","getLoginUserID"],"sourceRoot":"../../../src","sources":["services/message.ts"],"mappings":"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","ignoreList":[]}
@@ -88,6 +88,10 @@ export const useMessageStore = create()((set, get) => ({
88
88
  set(state => ({
89
89
  messages: loadMore ? mergeMessages(state.messages, res.messageList || []) : res.messageList || [],
90
90
  hasMoreOld: !res.isEnd,
91
+ // A fresh fetch is the newest page (FAB out of a jump window).
92
+ ...(loadMore ? {} : {
93
+ hasMoreNew: false
94
+ }),
91
95
  initLoading: false,
92
96
  moreOldLoading: false
93
97
  }));
@@ -145,9 +149,12 @@ export const useMessageStore = create()((set, get) => ({
145
149
  searchMessages: async ({
146
150
  applicationType,
147
151
  conversationId,
148
- startClientMsgId
152
+ startClientMsgId,
153
+ scrollOptions
149
154
  }) => {
150
155
  if (!conversationId || !startClientMsgId) return;
156
+ // Opted out of the toast-and-exit: the caller handles a missing target.
157
+ const keepOnUnavailable = scrollOptions?.exitOnUnavailable === false;
151
158
  try {
152
159
  set({
153
160
  initLoading: true
@@ -176,20 +183,34 @@ export const useMessageStore = create()((set, get) => ({
176
183
  if (conversationId === useConversationStore.getState().currentConversationId) {
177
184
  const currentMessages = searchMessages?.findResultItems?.find(item => item.conversationID === conversationId)?.messageList || [];
178
185
  const newMessages = mergeMessages([...(searchPrevRes?.messageList || []), ...currentMessages, ...(searchNextRes?.messageList || [])], []);
186
+ const targetMessage = newMessages.find(msg => msg.clientMsgID === startClientMsgId);
187
+
188
+ // Not in the local DB: keep the current window rather than swapping
189
+ // in one around a target that isn't there.
190
+ if (!targetMessage && keepOnUnavailable) {
191
+ set({
192
+ initLoading: false
193
+ });
194
+ return;
195
+ }
179
196
  set({
180
197
  messages: newMessages,
181
198
  hasMoreOld: !searchPrevRes.isEnd,
182
199
  hasMoreNew: !searchNextRes.isEnd,
183
200
  initLoading: false
184
201
  });
185
- const targetMessage = newMessages.find(msg => msg.clientMsgID === startClientMsgId);
186
202
  if (!targetMessage || isMessageRevoked(targetMessage)) {
187
- DeviceEventEmitter.emit(ChatEvent.TargetMessageUnavailable);
203
+ if (!keepOnUnavailable) {
204
+ DeviceEventEmitter.emit(ChatEvent.TargetMessageUnavailable);
205
+ }
188
206
  return;
189
207
  }
208
+ if (scrollOptions?.reemitAfterSearch === false) return;
190
209
  await new Promise(resolve => setTimeout(resolve, 300));
191
210
  DeviceEventEmitter.emit(ChatEvent.ScrollToMessage, {
192
- clientMessageId: startClientMsgId
211
+ ...scrollOptions,
212
+ clientMessageId: startClientMsgId,
213
+ fromSearch: true
193
214
  });
194
215
  }
195
216
  } catch (error) {