@droppii-org/chat-mobile 0.2.108 → 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.
- package/lib/module/screens/ChatInfo/ChatInfo.js +12 -3
- package/lib/module/screens/ChatInfo/ChatInfo.js.map +1 -1
- package/lib/module/screens/ChatInfo/ChatInfoActions.js +45 -40
- package/lib/module/screens/ChatInfo/ChatInfoActions.js.map +1 -1
- package/lib/module/screens/ChatInfo/ChatInfoProfile.js +11 -7
- package/lib/module/screens/ChatInfo/ChatInfoProfile.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetail.js +42 -7
- package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatListLegend.js +249 -49
- package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
- package/lib/module/screens/chat-detail/hooks/createUnreadMarkReadGate.js +32 -0
- package/lib/module/screens/chat-detail/hooks/createUnreadMarkReadGate.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useMarkConversationRead.js +180 -0
- package/lib/module/screens/chat-detail/hooks/useMarkConversationRead.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useUnreadAnchorSeen.js +88 -0
- package/lib/module/screens/chat-detail/hooks/useUnreadAnchorSeen.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useUnreadDeeplinkCheck.js +59 -0
- package/lib/module/screens/chat-detail/hooks/useUnreadDeeplinkCheck.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useUnreadIndicator.js +105 -0
- package/lib/module/screens/chat-detail/hooks/useUnreadIndicator.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useUnreadIndicatorInList.js +157 -0
- package/lib/module/screens/chat-detail/hooks/useUnreadIndicatorInList.js.map +1 -0
- package/lib/module/screens/chat-detail/legend/LegendChatUnreadButton.js +140 -0
- package/lib/module/screens/chat-detail/legend/LegendChatUnreadButton.js.map +1 -0
- package/lib/module/screens/chat-detail/legend/LegendChatUnreadDivider.js +51 -0
- package/lib/module/screens/chat-detail/legend/LegendChatUnreadDivider.js.map +1 -0
- package/lib/module/services/message.js +30 -0
- package/lib/module/services/message.js.map +1 -1
- package/lib/module/store/message.js +25 -4
- package/lib/module/store/message.js.map +1 -1
- package/lib/module/store/unreadIndicator.js +26 -0
- package/lib/module/store/unreadIndicator.js.map +1 -0
- package/lib/module/translation/resources/i18n.js +2 -0
- package/lib/module/translation/resources/i18n.js.map +1 -1
- package/lib/module/types/chat.js.map +1 -1
- package/lib/module/types/unreadIndicator.js +2 -0
- package/lib/module/types/unreadIndicator.js.map +1 -0
- package/lib/module/utils/markReadPolicy.js +25 -0
- package/lib/module/utils/markReadPolicy.js.map +1 -0
- package/lib/module/utils/promise.js +16 -0
- package/lib/module/utils/promise.js.map +1 -1
- package/lib/module/utils/unreadAnchor.js +57 -0
- package/lib/module/utils/unreadAnchor.js.map +1 -0
- package/lib/module/utils/unreadIndicatorState.js +96 -0
- package/lib/module/utils/unreadIndicatorState.js.map +1 -0
- package/lib/typescript/src/screens/ChatInfo/ChatInfo.d.ts.map +1 -1
- package/lib/typescript/src/screens/ChatInfo/ChatInfoActions.d.ts +2 -1
- package/lib/typescript/src/screens/ChatInfo/ChatInfoActions.d.ts.map +1 -1
- package/lib/typescript/src/screens/ChatInfo/ChatInfoProfile.d.ts +2 -1
- package/lib/typescript/src/screens/ChatInfo/ChatInfoProfile.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts +4 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/hooks/createUnreadMarkReadGate.d.ts +9 -0
- package/lib/typescript/src/screens/chat-detail/hooks/createUnreadMarkReadGate.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useMarkConversationRead.d.ts +39 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useMarkConversationRead.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useUnreadAnchorSeen.d.ts +26 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useUnreadAnchorSeen.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useUnreadDeeplinkCheck.d.ts +17 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useUnreadDeeplinkCheck.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useUnreadIndicator.d.ts +21 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useUnreadIndicator.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useUnreadIndicatorInList.d.ts +35 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useUnreadIndicatorInList.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatUnreadButton.d.ts +8 -0
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatUnreadButton.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatUnreadDivider.d.ts +7 -0
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatUnreadDivider.d.ts.map +1 -0
- package/lib/typescript/src/services/message.d.ts +8 -0
- package/lib/typescript/src/services/message.d.ts.map +1 -1
- package/lib/typescript/src/store/message.d.ts.map +1 -1
- package/lib/typescript/src/store/unreadIndicator.d.ts +10 -0
- package/lib/typescript/src/store/unreadIndicator.d.ts.map +1 -0
- package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
- package/lib/typescript/src/types/chat.d.ts +3 -0
- package/lib/typescript/src/types/chat.d.ts.map +1 -1
- package/lib/typescript/src/types/events.d.ts +17 -1
- package/lib/typescript/src/types/events.d.ts.map +1 -1
- package/lib/typescript/src/types/unreadIndicator.d.ts +45 -0
- package/lib/typescript/src/types/unreadIndicator.d.ts.map +1 -0
- package/lib/typescript/src/utils/markReadPolicy.d.ts +27 -0
- package/lib/typescript/src/utils/markReadPolicy.d.ts.map +1 -0
- package/lib/typescript/src/utils/promise.d.ts +6 -0
- package/lib/typescript/src/utils/promise.d.ts.map +1 -1
- package/lib/typescript/src/utils/unreadAnchor.d.ts +33 -0
- package/lib/typescript/src/utils/unreadAnchor.d.ts.map +1 -0
- package/lib/typescript/src/utils/unreadIndicatorState.d.ts +26 -0
- package/lib/typescript/src/utils/unreadIndicatorState.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/screens/ChatInfo/ChatInfo.tsx +10 -1
- package/src/screens/ChatInfo/ChatInfoActions.tsx +47 -39
- package/src/screens/ChatInfo/ChatInfoProfile.tsx +17 -12
- package/src/screens/chat-detail/ChatDetail.tsx +58 -5
- package/src/screens/chat-detail/ChatListLegend.tsx +314 -46
- package/src/screens/chat-detail/hooks/createUnreadMarkReadGate.ts +41 -0
- package/src/screens/chat-detail/hooks/useMarkConversationRead.ts +197 -0
- package/src/screens/chat-detail/hooks/useUnreadAnchorSeen.ts +105 -0
- package/src/screens/chat-detail/hooks/useUnreadDeeplinkCheck.ts +68 -0
- package/src/screens/chat-detail/hooks/useUnreadIndicator.ts +112 -0
- package/src/screens/chat-detail/hooks/useUnreadIndicatorInList.ts +181 -0
- package/src/screens/chat-detail/legend/LegendChatUnreadButton.tsx +165 -0
- package/src/screens/chat-detail/legend/LegendChatUnreadDivider.tsx +52 -0
- package/src/services/message.ts +42 -0
- package/src/store/message.ts +23 -5
- package/src/store/unreadIndicator.ts +38 -0
- package/src/translation/resources/i18n.ts +2 -0
- package/src/types/chat.ts +3 -0
- package/src/types/events.ts +18 -1
- package/src/types/unreadIndicator.ts +49 -0
- package/src/utils/markReadPolicy.ts +55 -0
- package/src/utils/promise.ts +20 -0
- package/src/utils/unreadAnchor.ts +87 -0
- package/src/utils/unreadIndicatorState.ts +131 -0
|
@@ -10,4 +10,10 @@ export type SettledResult<T> = {
|
|
|
10
10
|
* ship it, so a rejection would otherwise short-circuit Promise.all.
|
|
11
11
|
*/
|
|
12
12
|
export declare const allSettled: <T extends readonly unknown[]>(promises: readonly [...T]) => Promise<{ [K in keyof T]: SettledResult<Awaited<T[K]>>; }>;
|
|
13
|
+
/**
|
|
14
|
+
* Rejects with `Error('timeout')` if `promise` hasn't settled within `ms`.
|
|
15
|
+
* A settle that lands after the timeout is ignored, and the timer is always
|
|
16
|
+
* cleared on settle so nothing is left pending.
|
|
17
|
+
*/
|
|
18
|
+
export declare const withTimeout: <T>(promise: Promise<T>, ms: number) => Promise<T>;
|
|
13
19
|
//# sourceMappingURL=promise.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promise.d.ts","sourceRoot":"","sources":["../../../../src/utils/promise.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,CAAC,CAAC,IACvB;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACjC;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAE5C;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,SAAS,OAAO,EAAE,EACrD,UAAU,SAAS,CAAC,GAAG,CAAC,CAAC,KACxB,OAAO,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAE,CAOK,CAAC"}
|
|
1
|
+
{"version":3,"file":"promise.d.ts","sourceRoot":"","sources":["../../../../src/utils/promise.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,CAAC,CAAC,IACvB;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACjC;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAE5C;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,SAAS,OAAO,EAAE,EACrD,UAAU,SAAS,CAAC,GAAG,CAAC,CAAC,KACxB,OAAO,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAE,CAOK,CAAC;AAEjE;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,EAAE,SAAS,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,MAAM,KAAG,OAAO,CAAC,CAAC,CAatE,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { MessageItem } from '@droppii/openim-rn-client-sdk';
|
|
2
|
+
import type { UnreadAnchor } from '../types/unreadIndicator';
|
|
3
|
+
/** Height of the divider at the top of the anchor row (Figma: 27px). */
|
|
4
|
+
export declare const UNREAD_DIVIDER_HEIGHT = 27;
|
|
5
|
+
export declare const messageBelongsToConversation: (message: Pick<MessageItem, "sendID" | "recvID" | "groupID">, conversationId: string) => boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Validates the server's first-unread message before it's trusted: a finite
|
|
8
|
+
* positive `seq`, a non-empty `clientMsgID`, and a message that belongs to
|
|
9
|
+
* this conversation. Anything else → `null` (no indicator).
|
|
10
|
+
*/
|
|
11
|
+
export declare const toUnreadAnchor: (message: MessageItem | null | undefined, conversationId: string) => UnreadAnchor | null;
|
|
12
|
+
/**
|
|
13
|
+
* The list row that carries the divider: the first RENDERED message at or
|
|
14
|
+
* after the anchor. The anchor itself may be filtered out of the rendered list
|
|
15
|
+
* (internal / bot notification messages), so the match is by `seq` as well as
|
|
16
|
+
* by id. Only valid when the boundary is inside the loaded window — the raw
|
|
17
|
+
* window's oldest `seq` is at or before the anchor, or there's no older
|
|
18
|
+
* history — otherwise `null` (the anchor is out of window).
|
|
19
|
+
* Returns the row's list key (same as `keyExtractor` for real messages).
|
|
20
|
+
*/
|
|
21
|
+
export declare const resolveUnreadAnchorRowId: (rendered: readonly MessageItem[], anchor: UnreadAnchor | null, window: {
|
|
22
|
+
oldestLoadedSeq: number;
|
|
23
|
+
hasMoreOld: boolean;
|
|
24
|
+
}) => string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Whether the top edge of a row (where the divider sits) is inside the
|
|
27
|
+
* viewport with at least `minVisiblePx` of it showing. A tall row clipped at
|
|
28
|
+
* the top — only its bottom part visible — does NOT count.
|
|
29
|
+
*/
|
|
30
|
+
export declare const isRowTopInViewport: (rowTop: number | undefined, scroll: number, scrollLength: number, minVisiblePx?: number) => boolean;
|
|
31
|
+
/** D11: a deeplink to a message older than the first unread hides the indicator. */
|
|
32
|
+
export declare const isDeeplinkTargetBeforeAnchor: (targetSeq?: number, anchorSeq?: number) => boolean;
|
|
33
|
+
//# sourceMappingURL=unreadAnchor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unreadAnchor.d.ts","sourceRoot":"","sources":["../../../../src/utils/unreadAnchor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAO7D,wEAAwE;AACxE,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC,eAAO,MAAM,4BAA4B,GACvC,SAAS,IAAI,CAAC,WAAW,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC,EAC3D,gBAAgB,MAAM,KACrB,OAKmE,CAAC;AAEvE;;;;GAIG;AACH,eAAO,MAAM,cAAc,GACzB,SAAS,WAAW,GAAG,IAAI,GAAG,SAAS,EACvC,gBAAgB,MAAM,KACrB,YAAY,GAAG,IASjB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,GACnC,UAAU,SAAS,WAAW,EAAE,EAChC,QAAQ,YAAY,GAAG,IAAI,EAC3B,QAAQ;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,KACvD,MAAM,GAAG,IAWX,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,MAAM,GAAG,SAAS,EAC1B,QAAQ,MAAM,EACd,cAAc,MAAM,EACpB,qBAAoC,KACnC,OAG6C,CAAC;AAEjD,oFAAoF;AACpF,eAAO,MAAM,4BAA4B,GACvC,YAAY,MAAM,EAClB,YAAY,MAAM,KACjB,OACmE,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BeginUnreadSessionOptions, UnreadAnchor, UnreadIndicatorState } from '../types/unreadIndicator';
|
|
2
|
+
/**
|
|
3
|
+
* Pure transitions behind `useUnreadIndicatorStore` — kept free of React and
|
|
4
|
+
* zustand so they can be unit-tested. Every transition after `begin` is
|
|
5
|
+
* token-scoped: a token that isn't the current session's leaves the state
|
|
6
|
+
* untouched (same reference).
|
|
7
|
+
*/
|
|
8
|
+
export declare const UNREAD_GATE_WATCHDOG_SLACK_MS = 500;
|
|
9
|
+
export declare const INITIAL_UNREAD_INDICATOR_STATE: UnreadIndicatorState;
|
|
10
|
+
export declare const beginUnreadSession: (s: UnreadIndicatorState, { routeKey, conversationId, skip, awaitDeeplinkCheck, }: BeginUnreadSessionOptions, now: number) => UnreadIndicatorState;
|
|
11
|
+
export declare const resolveUnreadSession: (s: UnreadIndicatorState, token: number, anchor: UnreadAnchor | null) => UnreadIndicatorState;
|
|
12
|
+
export declare const clearUnreadSession: (s: UnreadIndicatorState, token: number) => UnreadIndicatorState;
|
|
13
|
+
export declare const dismissUnreadPill: (s: UnreadIndicatorState, token: number) => UnreadIndicatorState;
|
|
14
|
+
export declare const confirmUnreadDeeplinkCheck: (s: UnreadIndicatorState, token: number, hide: boolean) => UnreadIndicatorState;
|
|
15
|
+
export declare const rearmUnreadDeeplinkCheck: (s: UnreadIndicatorState, token: number) => UnreadIndicatorState;
|
|
16
|
+
export declare const resetUnreadSession: (s: UnreadIndicatorState) => UnreadIndicatorState;
|
|
17
|
+
/** When the watchdog opens a still-pending gate on its own. */
|
|
18
|
+
export declare const getUnreadGateDeadline: (s: UnreadIndicatorState, timeoutMs: number) => number;
|
|
19
|
+
/**
|
|
20
|
+
* Whether mark-read must wait. Closed only for MY session while its lookup is
|
|
21
|
+
* still pending and within the watchdog deadline. No session yet (not focused,
|
|
22
|
+
* or access not confirmed) is fail-closed; a session superseded by another
|
|
23
|
+
* visit is not this screen's gate.
|
|
24
|
+
*/
|
|
25
|
+
export declare const isUnreadGateClosed: (s: UnreadIndicatorState, myToken: number | null, now: number, timeoutMs: number) => boolean;
|
|
26
|
+
//# sourceMappingURL=unreadIndicatorState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unreadIndicatorState.d.ts","sourceRoot":"","sources":["../../../../src/utils/unreadIndicatorState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,YAAY,EACZ,oBAAoB,EACrB,MAAM,0BAA0B,CAAC;AAElC;;;;;GAKG;AAIH,eAAO,MAAM,6BAA6B,MAAM,CAAC;AAEjD,eAAO,MAAM,8BAA8B,EAAE,oBAU5C,CAAC;AAIF,eAAO,MAAM,kBAAkB,GAC7B,GAAG,oBAAoB,EACvB,yDAKG,yBAAyB,EAC5B,KAAK,MAAM,KACV,oBAQD,CAAC;AAEH,eAAO,MAAM,oBAAoB,GAC/B,GAAG,oBAAoB,EACvB,OAAO,MAAM,EACb,QAAQ,YAAY,GAAG,IAAI,KAC1B,oBAGyD,CAAC;AAI7D,eAAO,MAAM,kBAAkB,GAC7B,GAAG,oBAAoB,EACvB,OAAO,MAAM,KACZ,oBAUI,CAAC;AAER,eAAO,MAAM,iBAAiB,GAC5B,GAAG,oBAAoB,EACvB,OAAO,MAAM,KACZ,oBACuE,CAAC;AAE3E,eAAO,MAAM,0BAA0B,GACrC,GAAG,oBAAoB,EACvB,OAAO,MAAM,EACb,MAAM,OAAO,KACZ,oBAMF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,GAAG,oBAAoB,EACvB,OAAO,MAAM,KACZ,oBAGwC,CAAC;AAE5C,eAAO,MAAM,kBAAkB,GAC7B,GAAG,oBAAoB,KACtB,oBAGD,CAAC;AAEH,+DAA+D;AAC/D,eAAO,MAAM,qBAAqB,GAChC,GAAG,oBAAoB,EACvB,WAAW,MAAM,KAChB,MAAoE,CAAC;AAExE;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAC7B,GAAG,oBAAoB,EACvB,SAAS,MAAM,GAAG,IAAI,EACtB,KAAK,MAAM,EACX,WAAW,MAAM,KAChB,OAIF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@droppii-org/chat-mobile",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.110",
|
|
4
4
|
"description": "Droppii chat mobile",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"example-legacy": "yarn workspace @droppii-org/chat-mobile-example-legacy"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@droppii/openim-rn-client-sdk": "git+ssh://git@github.com/droppii/open-im-sdk-reactnative.git#v1.0.0-
|
|
73
|
+
"@droppii/openim-rn-client-sdk": "git+ssh://git@github.com/droppii/open-im-sdk-reactnative.git#v1.0.0-rc44",
|
|
74
74
|
"@legendapp/list": "^3.3.10",
|
|
75
75
|
"axios": "^1.16.1",
|
|
76
76
|
"lodash": "^4.18.1",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { memo, useCallback } from 'react';
|
|
1
|
+
import { memo, useCallback, useMemo } from 'react';
|
|
2
2
|
import { KContainer, KNavigation } from '@droppii/libs';
|
|
3
3
|
import { useNavigation } from '@react-navigation/native';
|
|
4
4
|
import { useGetConversationTarget } from '../../hooks/conversation/useGetConversationDetail';
|
|
@@ -14,6 +14,7 @@ import { trans } from '../../translation';
|
|
|
14
14
|
import { withChatDetailProvider } from '../../context';
|
|
15
15
|
import { useChatDetailContext } from '../../context';
|
|
16
16
|
import { getHeaderIconColor } from '../../utils/theme';
|
|
17
|
+
import { useIsJoinedGroup } from '../../hooks/useIsJoinedGroup';
|
|
17
18
|
|
|
18
19
|
const ChatInfo = memo(
|
|
19
20
|
({
|
|
@@ -39,6 +40,12 @@ const ChatInfo = memo(
|
|
|
39
40
|
);
|
|
40
41
|
const { leaveGroup } = useLeaveGroup();
|
|
41
42
|
|
|
43
|
+
const { data: isJoinedGroup } = useIsJoinedGroup(conversation?.groupID);
|
|
44
|
+
|
|
45
|
+
const isNotInGroup = useMemo(() => {
|
|
46
|
+
return isGroup ? !isJoinedGroup : false;
|
|
47
|
+
}, [isGroup, isJoinedGroup]);
|
|
48
|
+
|
|
42
49
|
const handleLeaveGroup = useCallback(async () => {
|
|
43
50
|
if (!conversation?.groupID) return;
|
|
44
51
|
|
|
@@ -78,6 +85,7 @@ const ChatInfo = memo(
|
|
|
78
85
|
<ChatInfoProfile
|
|
79
86
|
conversation={conversation}
|
|
80
87
|
onPressSearch={onPressSearch}
|
|
88
|
+
isNotInGroup={isNotInGroup}
|
|
81
89
|
/>
|
|
82
90
|
<ChatInfoMediaRow onPressMedia={onPressMedia} />
|
|
83
91
|
<KContainer.VisibleView visible={isGroup}>
|
|
@@ -92,6 +100,7 @@ const ChatInfo = memo(
|
|
|
92
100
|
onPressAddToGroup={onPressAddToGroup}
|
|
93
101
|
onPressLeaveGroup={handleLeaveGroup}
|
|
94
102
|
onDismissGroup={onDismissGroup}
|
|
103
|
+
isNotInGroup={isNotInGroup}
|
|
95
104
|
/>
|
|
96
105
|
</KContainer.ScrollView>
|
|
97
106
|
</KContainer.Page>
|
|
@@ -26,6 +26,7 @@ interface ChatInfoActionsProps {
|
|
|
26
26
|
onPressAddToGroup?: () => void;
|
|
27
27
|
onPressLeaveGroup?: () => void;
|
|
28
28
|
onDismissGroup?: () => void;
|
|
29
|
+
isNotInGroup?: boolean;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
const ChatInfoActions = memo(
|
|
@@ -38,6 +39,7 @@ const ChatInfoActions = memo(
|
|
|
38
39
|
onPressAddToGroup,
|
|
39
40
|
onPressLeaveGroup,
|
|
40
41
|
onDismissGroup,
|
|
42
|
+
isNotInGroup,
|
|
41
43
|
}: ChatInfoActionsProps) => {
|
|
42
44
|
const { pinConversation, unpinConversation } = usePinConversation();
|
|
43
45
|
const isPinned = Boolean(conversation?.isPinned);
|
|
@@ -113,22 +115,24 @@ const ChatInfoActions = memo(
|
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
{
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
118
|
+
if (!isNotInGroup) {
|
|
119
|
+
items.push({
|
|
120
|
+
leftNode: {
|
|
121
|
+
icon: { vectorName: isPinned ? 'thumbtack-off-o' : 'thumbtack-o' },
|
|
122
|
+
},
|
|
123
|
+
onPress: handlePinPress,
|
|
124
|
+
showChevron: false,
|
|
125
|
+
paddingH: '0.75rem',
|
|
126
|
+
paddingV: '0.75rem',
|
|
127
|
+
customLabel: (
|
|
128
|
+
<KLabel.Text typo="TextMdMedium" numberOfLines={1}>
|
|
129
|
+
{isPinned
|
|
130
|
+
? trans('chat:info_unpin_message')
|
|
131
|
+
: trans('chat:info_pin_message')}
|
|
132
|
+
</KLabel.Text>
|
|
133
|
+
),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
132
136
|
|
|
133
137
|
return items;
|
|
134
138
|
}, [
|
|
@@ -141,11 +145,12 @@ const ChatInfoActions = memo(
|
|
|
141
145
|
onPressCreateGroup,
|
|
142
146
|
onPressAddToGroup,
|
|
143
147
|
handlePinPress,
|
|
148
|
+
isNotInGroup,
|
|
144
149
|
]);
|
|
145
150
|
|
|
146
151
|
const dangerData: KListItemBaseItemProps[] = useMemo(() => {
|
|
147
152
|
const items: KListItemBaseItemProps[] = [];
|
|
148
|
-
if (isGroup) {
|
|
153
|
+
if (isGroup && !isNotInGroup) {
|
|
149
154
|
items.push({
|
|
150
155
|
leftNode: {
|
|
151
156
|
icon: {
|
|
@@ -194,29 +199,31 @@ const ChatInfoActions = memo(
|
|
|
194
199
|
}
|
|
195
200
|
}
|
|
196
201
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
202
|
+
if (!isNotInGroup) {
|
|
203
|
+
items.push({
|
|
204
|
+
label: trans('chat:info_delete_history'),
|
|
205
|
+
leftNode: {
|
|
206
|
+
icon: {
|
|
207
|
+
vectorName: 'trash-alt-o-1',
|
|
208
|
+
tintColor: KColors.danger.normal,
|
|
209
|
+
},
|
|
203
210
|
},
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
)
|
|
219
|
-
}
|
|
211
|
+
tintColor: KColors.danger.normal,
|
|
212
|
+
onPress: handleDeleteHistory,
|
|
213
|
+
showChevron: false,
|
|
214
|
+
paddingH: '0.75rem',
|
|
215
|
+
paddingV: '0.75rem',
|
|
216
|
+
customLabel: (
|
|
217
|
+
<KLabel.Text
|
|
218
|
+
typo="TextMdMedium"
|
|
219
|
+
color={KColors.danger.normal}
|
|
220
|
+
numberOfLines={1}
|
|
221
|
+
>
|
|
222
|
+
{trans('chat:info_delete_history')}
|
|
223
|
+
</KLabel.Text>
|
|
224
|
+
),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
220
227
|
|
|
221
228
|
return items;
|
|
222
229
|
}, [
|
|
@@ -225,6 +232,7 @@ const ChatInfoActions = memo(
|
|
|
225
232
|
handleDeleteHistory,
|
|
226
233
|
isOwner,
|
|
227
234
|
handleDismissGroup,
|
|
235
|
+
isNotInGroup,
|
|
228
236
|
]);
|
|
229
237
|
|
|
230
238
|
return (
|
|
@@ -47,10 +47,11 @@ const QuickAction = memo(
|
|
|
47
47
|
interface ChatInfoProfileProps {
|
|
48
48
|
conversation?: ConversationItem;
|
|
49
49
|
onPressSearch?: () => void;
|
|
50
|
+
isNotInGroup?: boolean;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
const ChatInfoProfile = memo(
|
|
53
|
-
({ conversation, onPressSearch }: ChatInfoProfileProps) => {
|
|
54
|
+
({ conversation, onPressSearch, isNotInGroup }: ChatInfoProfileProps) => {
|
|
54
55
|
const { faceURL, showName = '' } = conversation ?? {};
|
|
55
56
|
|
|
56
57
|
const isMuted =
|
|
@@ -122,17 +123,21 @@ const ChatInfoProfile = memo(
|
|
|
122
123
|
background={KColors.palette.gray.w25}
|
|
123
124
|
iconColor={KColors.gray.dark}
|
|
124
125
|
/>
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
126
|
+
<KContainer.VisibleView visible={!isNotInGroup}>
|
|
127
|
+
<QuickAction
|
|
128
|
+
iconName={isMuted ? 'bell-b' : 'bell-off-o'}
|
|
129
|
+
label={
|
|
130
|
+
isMuted
|
|
131
|
+
? trans('chat:unmute_action')
|
|
132
|
+
: trans('chat:mute_action')
|
|
133
|
+
}
|
|
134
|
+
background={
|
|
135
|
+
isMuted ? KColors.primary.normal : KColors.palette.gray.w25
|
|
136
|
+
}
|
|
137
|
+
iconColor={isMuted ? KColors.white : KColors.gray.dark}
|
|
138
|
+
onPress={handleNotificationPress}
|
|
139
|
+
/>
|
|
140
|
+
</KContainer.VisibleView>
|
|
136
141
|
</KContainer.View>
|
|
137
142
|
</KContainer.View>
|
|
138
143
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { memo, useCallback, useEffect, useRef } from 'react';
|
|
1
|
+
import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
|
2
2
|
import { DeviceEventEmitter } from 'react-native';
|
|
3
3
|
|
|
4
4
|
import { useKeyboardScrollToEnd } from '@legendapp/list/keyboard';
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
} from '../../hooks/conversation/useGetConversationDetail';
|
|
41
41
|
import {
|
|
42
42
|
GroupJoinSource,
|
|
43
|
+
PeerType,
|
|
43
44
|
type MessageItem,
|
|
44
45
|
} from '@droppii/openim-rn-client-sdk';
|
|
45
46
|
import { useMessages } from '../../hooks/message/useMessages';
|
|
@@ -51,7 +52,12 @@ import { useSingleAccessGuard } from '../../hooks/conversation/useSingleAccessGu
|
|
|
51
52
|
import { useIsJoinedGroup } from '../../hooks/useIsJoinedGroup';
|
|
52
53
|
import { UIUtils } from '../../utils/UIUtils';
|
|
53
54
|
import { trans } from '../../translation';
|
|
54
|
-
import {
|
|
55
|
+
import {
|
|
56
|
+
getGroupIdFromConversationId,
|
|
57
|
+
isConversationParticipant,
|
|
58
|
+
isSingleConversationId,
|
|
59
|
+
} from '../../utils/conversation';
|
|
60
|
+
import { useUnreadIndicator } from './hooks/useUnreadIndicator';
|
|
55
61
|
import { ChatDetailLoading } from './components/ChatDetailLoading';
|
|
56
62
|
|
|
57
63
|
interface ChatDetailScreenProps extends Omit<
|
|
@@ -111,6 +117,25 @@ const ChatDetail = memo(
|
|
|
111
117
|
[]
|
|
112
118
|
);
|
|
113
119
|
|
|
120
|
+
// Bot from the conversation itself: ChatDetailContext's flag is derived
|
|
121
|
+
// from an id this screen sets in an effect, so it's still false on the
|
|
122
|
+
// first effect pass.
|
|
123
|
+
const isBotThread = conversation?.peerType === PeerType.Bot;
|
|
124
|
+
// 1v1: only a participant may look up / mark read. Groups: the gate's
|
|
125
|
+
// `isReady` already enforced join/access before this screen mounted.
|
|
126
|
+
const accessConfirmed =
|
|
127
|
+
!!currentUser?.userID &&
|
|
128
|
+
(!isSingleConversationId(conversationId) ||
|
|
129
|
+
isConversationParticipant(conversationId, currentUser.userID));
|
|
130
|
+
// Must stay above useMessages (see useUnreadDeeplinkCheck).
|
|
131
|
+
const { unreadToken, unreadTokenRef, markChildNavigation } =
|
|
132
|
+
useUnreadIndicator({
|
|
133
|
+
conversationId,
|
|
134
|
+
isBotConversation: isBotThread,
|
|
135
|
+
targetMessageId,
|
|
136
|
+
accessConfirmed,
|
|
137
|
+
});
|
|
138
|
+
|
|
114
139
|
const {
|
|
115
140
|
messages,
|
|
116
141
|
initLoading,
|
|
@@ -222,9 +247,34 @@ const ChatDetail = memo(
|
|
|
222
247
|
});
|
|
223
248
|
}, [currentUser, unpinMessage, customMessageItemFactory]);
|
|
224
249
|
|
|
250
|
+
// Library-initiated child navigation keeps the unread indicator across the
|
|
251
|
+
// blur it causes; markChildNavigation must run before the callback does.
|
|
225
252
|
const handlePressMenu = useCallback(() => {
|
|
253
|
+
markChildNavigation();
|
|
226
254
|
onPressMenu?.(conversationId);
|
|
227
|
-
}, [conversationId, onPressMenu]);
|
|
255
|
+
}, [conversationId, onPressMenu, markChildNavigation]);
|
|
256
|
+
|
|
257
|
+
const handlePressSearch = useMemo(
|
|
258
|
+
() =>
|
|
259
|
+
onPressSearch
|
|
260
|
+
? (id: string) => {
|
|
261
|
+
markChildNavigation();
|
|
262
|
+
onPressSearch(id);
|
|
263
|
+
}
|
|
264
|
+
: undefined,
|
|
265
|
+
[onPressSearch, markChildNavigation]
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const handlePressAddMember = useMemo(
|
|
269
|
+
() =>
|
|
270
|
+
onPressAddMember
|
|
271
|
+
? () => {
|
|
272
|
+
markChildNavigation();
|
|
273
|
+
onPressAddMember();
|
|
274
|
+
}
|
|
275
|
+
: undefined,
|
|
276
|
+
[onPressAddMember, markChildNavigation]
|
|
277
|
+
);
|
|
228
278
|
|
|
229
279
|
const handlePressJoinGroup = useCallback(() => {
|
|
230
280
|
if (!conversation?.groupID) return;
|
|
@@ -248,8 +298,8 @@ const ChatDetail = memo(
|
|
|
248
298
|
targetUserId={targetUserId}
|
|
249
299
|
showAddMember={resolvedShowAddMember}
|
|
250
300
|
onBack={onBack ?? handleBackDefault}
|
|
251
|
-
onPressSearch={
|
|
252
|
-
onPressAddMember={
|
|
301
|
+
onPressSearch={handlePressSearch}
|
|
302
|
+
onPressAddMember={handlePressAddMember}
|
|
253
303
|
onPressMenu={handlePressMenu}
|
|
254
304
|
onPressAvatar={handlePressMenu}
|
|
255
305
|
/>
|
|
@@ -283,6 +333,9 @@ const ChatDetail = memo(
|
|
|
283
333
|
dismissAreaTouchProps={dismissAreaTouchProps}
|
|
284
334
|
isComposerPanelOpen={isComposerPanelOpen}
|
|
285
335
|
onDismissAreaScrollBeginDrag={notifyScrollBeginDrag}
|
|
336
|
+
unreadToken={unreadToken}
|
|
337
|
+
unreadTokenRef={unreadTokenRef}
|
|
338
|
+
deeplinkTargetId={targetMessageId || undefined}
|
|
286
339
|
/>
|
|
287
340
|
</TimelinePressGuardProvider>
|
|
288
341
|
|