@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,131 @@
1
+ import type {
2
+ BeginUnreadSessionOptions,
3
+ UnreadAnchor,
4
+ UnreadIndicatorState,
5
+ } from '../types/unreadIndicator';
6
+
7
+ /**
8
+ * Pure transitions behind `useUnreadIndicatorStore` — kept free of React and
9
+ * zustand so they can be unit-tested. Every transition after `begin` is
10
+ * token-scoped: a token that isn't the current session's leaves the state
11
+ * untouched (same reference).
12
+ */
13
+
14
+ // Slack past the lookup timeout before the gate treats itself as open even if
15
+ // `resolve` never ran.
16
+ export const UNREAD_GATE_WATCHDOG_SLACK_MS = 500;
17
+
18
+ export const INITIAL_UNREAD_INDICATOR_STATE: UnreadIndicatorState = {
19
+ token: 0,
20
+ routeKey: undefined,
21
+ conversationId: undefined,
22
+ pending: false,
23
+ pendingSince: 0,
24
+ anchor: null,
25
+ pillDismissed: false,
26
+ awaitingDeeplinkCheck: false,
27
+ settledBy: null,
28
+ };
29
+
30
+ const isMine = (s: UnreadIndicatorState, token: number) => token === s.token;
31
+
32
+ export const beginUnreadSession = (
33
+ s: UnreadIndicatorState,
34
+ {
35
+ routeKey,
36
+ conversationId,
37
+ skip,
38
+ awaitDeeplinkCheck,
39
+ }: BeginUnreadSessionOptions,
40
+ now: number
41
+ ): UnreadIndicatorState => ({
42
+ ...INITIAL_UNREAD_INDICATOR_STATE,
43
+ token: s.token + 1,
44
+ routeKey,
45
+ conversationId,
46
+ pending: !skip,
47
+ pendingSince: skip ? 0 : now,
48
+ awaitingDeeplinkCheck: !skip && awaitDeeplinkCheck,
49
+ });
50
+
51
+ export const resolveUnreadSession = (
52
+ s: UnreadIndicatorState,
53
+ token: number,
54
+ anchor: UnreadAnchor | null
55
+ ): UnreadIndicatorState =>
56
+ !isMine(s, token) || !s.pending
57
+ ? s
58
+ : { ...s, pending: false, anchor, settledBy: 'resolve' };
59
+
60
+ // Doesn't bump the token: a late `resolve` is already ignored because
61
+ // `pending` is false. Only a new `begin` (a new visit) bumps it.
62
+ export const clearUnreadSession = (
63
+ s: UnreadIndicatorState,
64
+ token: number
65
+ ): UnreadIndicatorState =>
66
+ !isMine(s, token)
67
+ ? s
68
+ : {
69
+ ...s,
70
+ pending: false,
71
+ anchor: null,
72
+ pillDismissed: true,
73
+ awaitingDeeplinkCheck: false,
74
+ settledBy: 'clear',
75
+ };
76
+
77
+ export const dismissUnreadPill = (
78
+ s: UnreadIndicatorState,
79
+ token: number
80
+ ): UnreadIndicatorState =>
81
+ !isMine(s, token) || s.pillDismissed ? s : { ...s, pillDismissed: true };
82
+
83
+ export const confirmUnreadDeeplinkCheck = (
84
+ s: UnreadIndicatorState,
85
+ token: number,
86
+ hide: boolean
87
+ ): UnreadIndicatorState => {
88
+ if (!isMine(s, token)) return s;
89
+ // Not a clear: the user is still on the screen, so it must not trigger
90
+ // the leave flush that a blur's clear does.
91
+ if (hide) return { ...clearUnreadSession(s, token), settledBy: 'hide' };
92
+ return s.awaitingDeeplinkCheck ? { ...s, awaitingDeeplinkCheck: false } : s;
93
+ };
94
+
95
+ export const rearmUnreadDeeplinkCheck = (
96
+ s: UnreadIndicatorState,
97
+ token: number
98
+ ): UnreadIndicatorState =>
99
+ !isMine(s, token) || !s.anchor || s.awaitingDeeplinkCheck
100
+ ? s
101
+ : { ...s, awaitingDeeplinkCheck: true };
102
+
103
+ export const resetUnreadSession = (
104
+ s: UnreadIndicatorState
105
+ ): UnreadIndicatorState => ({
106
+ ...INITIAL_UNREAD_INDICATOR_STATE,
107
+ token: s.token + 1,
108
+ });
109
+
110
+ /** When the watchdog opens a still-pending gate on its own. */
111
+ export const getUnreadGateDeadline = (
112
+ s: UnreadIndicatorState,
113
+ timeoutMs: number
114
+ ): number => s.pendingSince + timeoutMs + UNREAD_GATE_WATCHDOG_SLACK_MS;
115
+
116
+ /**
117
+ * Whether mark-read must wait. Closed only for MY session while its lookup is
118
+ * still pending and within the watchdog deadline. No session yet (not focused,
119
+ * or access not confirmed) is fail-closed; a session superseded by another
120
+ * visit is not this screen's gate.
121
+ */
122
+ export const isUnreadGateClosed = (
123
+ s: UnreadIndicatorState,
124
+ myToken: number | null,
125
+ now: number,
126
+ timeoutMs: number
127
+ ): boolean => {
128
+ if (myToken == null) return true;
129
+ if (!isMine(s, myToken)) return false;
130
+ return s.pending && now <= getUnreadGateDeadline(s, timeoutMs);
131
+ };