@droppii-org/chat-mobile 0.2.76 → 0.2.78
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/components/messages/groupNotificationMessage/index.js +5 -3
- package/lib/module/components/messages/groupNotificationMessage/index.js.map +1 -1
- package/lib/module/context/ChatDetailContext.js +10 -1
- package/lib/module/context/ChatDetailContext.js.map +1 -1
- package/lib/module/core/useChatListener.js +8 -5
- package/lib/module/core/useChatListener.js.map +1 -1
- package/lib/module/hooks/groups/useLeaveGroup.js +93 -0
- package/lib/module/hooks/groups/useLeaveGroup.js.map +1 -0
- package/lib/module/hooks/message/useSendMessage.js +16 -8
- package/lib/module/hooks/message/useSendMessage.js.map +1 -1
- package/lib/module/hooks/query-keys.js +2 -1
- package/lib/module/hooks/query-keys.js.map +1 -1
- package/lib/module/hooks/useConversationList.js +6 -9
- package/lib/module/hooks/useConversationList.js.map +1 -1
- package/lib/module/hooks/useGroupPermission.js +39 -5
- package/lib/module/hooks/useGroupPermission.js.map +1 -1
- package/lib/module/screens/ChatInfo/ChatInfo.js +15 -5
- package/lib/module/screens/ChatInfo/ChatInfo.js.map +1 -1
- package/lib/module/screens/ChatInfo/ChatInfoDescriptionRow.js +2 -1
- package/lib/module/screens/ChatInfo/ChatInfoDescriptionRow.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatComposer.js +17 -8
- package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetailHeader.js +4 -8
- package/lib/module/screens/chat-detail/ChatDetailHeader.js.map +1 -1
- package/lib/module/translation/resources/i18n.js +21 -1
- package/lib/module/translation/resources/i18n.js.map +1 -1
- package/lib/module/types/chat.js.map +1 -1
- package/lib/module/utils/legendListMessage.js +5 -1
- package/lib/module/utils/legendListMessage.js.map +1 -1
- package/lib/module/utils/messageUtils.js +117 -8
- package/lib/module/utils/messageUtils.js.map +1 -1
- package/lib/module/utils/toastFlashMessage.js +6 -0
- package/lib/module/utils/toastFlashMessage.js.map +1 -1
- package/lib/typescript/src/components/messages/groupNotificationMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/context/ChatDetailContext.d.ts.map +1 -1
- package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
- package/lib/typescript/src/hooks/groups/useLeaveGroup.d.ts +6 -0
- package/lib/typescript/src/hooks/groups/useLeaveGroup.d.ts.map +1 -0
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/src/hooks/query-keys.d.ts +1 -0
- package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useConversationList.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useGroupPermission.d.ts +6 -1
- package/lib/typescript/src/hooks/useGroupPermission.d.ts.map +1 -1
- package/lib/typescript/src/screens/ChatInfo/ChatInfo.d.ts.map +1 -1
- package/lib/typescript/src/screens/ChatInfo/ChatInfoDescriptionRow.d.ts +1 -2
- package/lib/typescript/src/screens/ChatInfo/ChatInfoDescriptionRow.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetailHeader.d.ts.map +1 -1
- package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
- package/lib/typescript/src/types/chat.d.ts +3 -1
- package/lib/typescript/src/types/chat.d.ts.map +1 -1
- package/lib/typescript/src/utils/legendListMessage.d.ts.map +1 -1
- package/lib/typescript/src/utils/messageUtils.d.ts +6 -0
- package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
- package/lib/typescript/src/utils/toastFlashMessage.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/components/messages/groupNotificationMessage/index.tsx +8 -4
- package/src/context/ChatDetailContext.tsx +9 -0
- package/src/core/useChatListener.ts +15 -4
- package/src/hooks/groups/useLeaveGroup.ts +104 -0
- package/src/hooks/message/useSendMessage.ts +15 -12
- package/src/hooks/query-keys.ts +1 -0
- package/src/hooks/useConversationList.ts +8 -17
- package/src/hooks/useGroupPermission.ts +46 -5
- package/src/screens/ChatInfo/ChatInfo.tsx +26 -8
- package/src/screens/ChatInfo/ChatInfoDescriptionRow.tsx +3 -2
- package/src/screens/chat-detail/ChatComposer.tsx +21 -25
- package/src/screens/chat-detail/ChatDetailHeader.tsx +4 -19
- package/src/translation/resources/i18n.ts +29 -1
- package/src/types/chat.ts +3 -0
- package/src/utils/legendListMessage.ts +5 -1
- package/src/utils/messageUtils.ts +127 -7
- package/src/utils/toastFlashMessage.ts +8 -2
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import OpenIMSDK, {
|
|
2
|
+
GroupMemberRole,
|
|
3
|
+
GroupVisibility,
|
|
4
|
+
} from '@droppii/openim-rn-client-sdk';
|
|
5
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
6
|
+
import { conversationQueryKeys, groupQueryKeys } from '../query-keys';
|
|
7
|
+
import { UIUtils } from '../../utils/UIUtils';
|
|
8
|
+
import { trans } from '../../translation';
|
|
9
|
+
|
|
10
|
+
const showOwnerBlockedModal = () => {
|
|
11
|
+
UIUtils.alert.open({
|
|
12
|
+
title: trans('chat:leave_group_blocked_title'),
|
|
13
|
+
message: trans('chat:leave_group_blocked_desc'),
|
|
14
|
+
primaryIndex: 0,
|
|
15
|
+
buttonType: 'horizontal-button',
|
|
16
|
+
buttons: [
|
|
17
|
+
{ label: trans('chat:leave_group_blocked_button'), kind: 'primary' },
|
|
18
|
+
],
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const confirmLeaveGroup = (visibility?: GroupVisibility) =>
|
|
23
|
+
new Promise<void>((resolve, reject) => {
|
|
24
|
+
console.log('confirmLeaveGroup', visibility);
|
|
25
|
+
UIUtils.alert.open({
|
|
26
|
+
title: trans('chat:leave_group_confirm_title'),
|
|
27
|
+
message: trans(
|
|
28
|
+
visibility === GroupVisibility.Public
|
|
29
|
+
? 'chat:leave_group_confirm_desc_public'
|
|
30
|
+
: 'chat:leave_group_confirm_desc_private'
|
|
31
|
+
),
|
|
32
|
+
primaryIndex: 1,
|
|
33
|
+
buttonType: 'horizontal-button',
|
|
34
|
+
buttons: [
|
|
35
|
+
{
|
|
36
|
+
label: trans('chat:leave_group_confirm_secondary'),
|
|
37
|
+
kind: 'light',
|
|
38
|
+
onPress: reject,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
label: trans('chat:leave_group_confirm_primary'),
|
|
42
|
+
kind: 'danger',
|
|
43
|
+
onPress: resolve,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export const useLeaveGroup = () => {
|
|
50
|
+
const queryClient = useQueryClient();
|
|
51
|
+
|
|
52
|
+
const mutation = useMutation({
|
|
53
|
+
mutationKey: groupQueryKeys.leave(),
|
|
54
|
+
mutationFn: (groupID: string) => OpenIMSDK.quitGroup(groupID),
|
|
55
|
+
onSuccess: (_data, groupID) => {
|
|
56
|
+
queryClient.invalidateQueries({
|
|
57
|
+
queryKey: conversationQueryKeys.joinedGroup(groupID),
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
onError: () => {
|
|
61
|
+
UIUtils.toast.open({
|
|
62
|
+
title: trans('chat:generic_error'),
|
|
63
|
+
theme: 'error',
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const leaveGroup = async (groupID: string, visibility?: GroupVisibility) => {
|
|
69
|
+
try {
|
|
70
|
+
// The owner must transfer ownership before leaving — block instead of
|
|
71
|
+
// showing the confirm modal.
|
|
72
|
+
const selfInfo = await OpenIMSDK.getSelfUserInfo(groupID);
|
|
73
|
+
if (selfInfo?.roleLevel === GroupMemberRole.Owner) {
|
|
74
|
+
showOwnerBlockedModal();
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
await confirmLeaveGroup(visibility);
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Revalidate right before submitting — role/membership may have
|
|
84
|
+
// changed while the confirm modal was open.
|
|
85
|
+
const latestSelfInfo = await OpenIMSDK.getSelfUserInfo(groupID);
|
|
86
|
+
if (latestSelfInfo?.roleLevel === GroupMemberRole.Owner) {
|
|
87
|
+
showOwnerBlockedModal();
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await mutation.mutateAsync(groupID);
|
|
92
|
+
return true;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error('leaveGroup failed', error);
|
|
95
|
+
UIUtils.toast.open({
|
|
96
|
+
title: trans('chat:generic_error'),
|
|
97
|
+
theme: 'error',
|
|
98
|
+
});
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return { leaveGroup, isLeavingGroup: mutation.isPending };
|
|
104
|
+
};
|
|
@@ -9,7 +9,6 @@ import type {
|
|
|
9
9
|
SendMsgParams,
|
|
10
10
|
} from '@droppii/openim-rn-client-sdk';
|
|
11
11
|
import {
|
|
12
|
-
GroupPermission,
|
|
13
12
|
MessageStatus,
|
|
14
13
|
MessageType,
|
|
15
14
|
SessionType,
|
|
@@ -34,8 +33,8 @@ import type { StickerItem } from '../../types/sticker';
|
|
|
34
33
|
import { useQueryClient } from '@tanstack/react-query';
|
|
35
34
|
import { stickerQueryKeys } from '../query-keys';
|
|
36
35
|
import { getMessagePreview } from '../../utils/messageUtils';
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
36
|
+
import { useCanSendMessage } from '../useGroupPermission';
|
|
37
|
+
import { UIUtils } from '../../utils/UIUtils';
|
|
39
38
|
|
|
40
39
|
const buildSendParams = (
|
|
41
40
|
message: MessageItem,
|
|
@@ -61,6 +60,8 @@ export const useSendMessage = () => {
|
|
|
61
60
|
const updateOneMessage = useMessageStore((state) => state.updateOneMessage);
|
|
62
61
|
const { quotedMessage, setQuotedMessage } = useChatDetailContext();
|
|
63
62
|
const queryClient = useQueryClient();
|
|
63
|
+
const { allowed: isSendMessageAllowed, reason: sendDeniedReason } =
|
|
64
|
+
useCanSendMessage();
|
|
64
65
|
|
|
65
66
|
const dispatchSend = useCallback(
|
|
66
67
|
async (
|
|
@@ -104,6 +105,14 @@ export const useSendMessage = () => {
|
|
|
104
105
|
customMessage?: CustomMessagePayload;
|
|
105
106
|
sticker?: StickerItem;
|
|
106
107
|
}) => {
|
|
108
|
+
if (!isSendMessageAllowed) {
|
|
109
|
+
UIUtils.toast.open({
|
|
110
|
+
title: sendDeniedReason,
|
|
111
|
+
theme: 'info',
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
107
116
|
const currentConversation = useConversationStore
|
|
108
117
|
.getState()
|
|
109
118
|
.getCurrentConversation();
|
|
@@ -188,6 +197,8 @@ export const useSendMessage = () => {
|
|
|
188
197
|
quotedMessage,
|
|
189
198
|
setQuotedMessage,
|
|
190
199
|
queryClient,
|
|
200
|
+
isSendMessageAllowed,
|
|
201
|
+
sendDeniedReason,
|
|
191
202
|
]
|
|
192
203
|
);
|
|
193
204
|
|
|
@@ -252,16 +263,8 @@ export const useSendMessage = () => {
|
|
|
252
263
|
markMessageRevoked(message.clientMsgID);
|
|
253
264
|
}, []);
|
|
254
265
|
|
|
255
|
-
const sendMessageWithPermission = useGuardedAction(
|
|
256
|
-
GroupPermission.SendMessage,
|
|
257
|
-
sendMessage,
|
|
258
|
-
{
|
|
259
|
-
deniedMessage: trans('chat:no_send_permission'),
|
|
260
|
-
}
|
|
261
|
-
);
|
|
262
|
-
|
|
263
266
|
return {
|
|
264
|
-
sendMessage
|
|
267
|
+
sendMessage,
|
|
265
268
|
retryMessage,
|
|
266
269
|
revokeMessage,
|
|
267
270
|
};
|
package/src/hooks/query-keys.ts
CHANGED
|
@@ -23,26 +23,17 @@ export function useConversationList({
|
|
|
23
23
|
const query = useQuery({
|
|
24
24
|
queryKey: [...conversationQueryKeys.list(applicationType), page],
|
|
25
25
|
enabled: enabled !== false,
|
|
26
|
+
staleTime: 1000 * 60 * 1, // 5 minutes
|
|
26
27
|
queryFn: async () => {
|
|
27
28
|
const offset = (page - 1) * 500;
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}),
|
|
34
|
-
OpenIMSDK.getJoinedGroupList(),
|
|
35
|
-
]);
|
|
29
|
+
const conversations = await OpenIMSDK.getConversationListSplitApp({
|
|
30
|
+
offset,
|
|
31
|
+
count: 500,
|
|
32
|
+
applicationType,
|
|
33
|
+
});
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
conversation.groupID && !joinedGroupIds.has(conversation.groupID)
|
|
40
|
-
? { ...conversation, isNotInGroup: true }
|
|
41
|
-
: conversation
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
useConversationStore.getState().updateConversations(resolved);
|
|
45
|
-
return resolved;
|
|
35
|
+
useConversationStore.getState().updateConversations(conversations);
|
|
36
|
+
return conversations;
|
|
46
37
|
},
|
|
47
38
|
});
|
|
48
39
|
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
GroupMemberRole,
|
|
3
|
+
GroupPermission,
|
|
4
|
+
GroupStatus,
|
|
5
|
+
} from '@droppii/openim-rn-client-sdk';
|
|
2
6
|
import { useChatDetailContext } from '../context';
|
|
3
7
|
import { useCallback, useMemo } from 'react';
|
|
4
8
|
import { trans } from '../translation';
|
|
@@ -8,18 +12,17 @@ export const useHasGroupPermission = (
|
|
|
8
12
|
permission: GroupPermission | GroupPermission[],
|
|
9
13
|
mode: 'every' | 'some' = 'every'
|
|
10
14
|
): boolean => {
|
|
11
|
-
const {
|
|
12
|
-
const isNotGroup = conversation?.peerType !== PeerType.Group;
|
|
15
|
+
const { isGroupConversation, userInfo } = useChatDetailContext();
|
|
13
16
|
const required = Array.isArray(permission) ? permission : [permission];
|
|
14
17
|
const granted = userInfo?.permissions || [];
|
|
15
18
|
|
|
16
19
|
return useMemo(() => {
|
|
17
|
-
if (
|
|
20
|
+
if (!isGroupConversation) return true;
|
|
18
21
|
return mode === 'every'
|
|
19
22
|
? required.every((p) => granted?.includes(p))
|
|
20
23
|
: required.some((p) => granted?.includes(p));
|
|
21
24
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
22
|
-
}, [
|
|
25
|
+
}, [isGroupConversation, mode]);
|
|
23
26
|
};
|
|
24
27
|
|
|
25
28
|
export const useGuardedAction = <T extends (...args: any[]) => any>(
|
|
@@ -42,3 +45,41 @@ export const useGuardedAction = <T extends (...args: any[]) => any>(
|
|
|
42
45
|
[allowed, action, options?.deniedMessage]
|
|
43
46
|
) as T;
|
|
44
47
|
};
|
|
48
|
+
|
|
49
|
+
export const useHasRoleLevel = (minRoleLevel: GroupMemberRole): boolean => {
|
|
50
|
+
const { isGroupConversation, userInfo } = useChatDetailContext();
|
|
51
|
+
|
|
52
|
+
return useMemo(() => {
|
|
53
|
+
if (!isGroupConversation) return true;
|
|
54
|
+
return (userInfo?.roleLevel ?? GroupMemberRole.Normal) >= minRoleLevel;
|
|
55
|
+
}, [isGroupConversation, userInfo?.roleLevel, minRoleLevel]);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const useCanSendMessage = (): {
|
|
59
|
+
allowed: boolean;
|
|
60
|
+
reason?: string;
|
|
61
|
+
} => {
|
|
62
|
+
const { userInfo, groupInfo, isGroupConversation } = useChatDetailContext();
|
|
63
|
+
|
|
64
|
+
const hasRolePermission = useMemo(
|
|
65
|
+
() => userInfo?.permissions?.includes(GroupPermission.SendMessage) ?? false,
|
|
66
|
+
[userInfo?.permissions]
|
|
67
|
+
);
|
|
68
|
+
const isOwnerOrAdmin = useHasRoleLevel(GroupMemberRole.Admin);
|
|
69
|
+
|
|
70
|
+
if (!isGroupConversation) return { allowed: true };
|
|
71
|
+
|
|
72
|
+
if (!hasRolePermission)
|
|
73
|
+
return {
|
|
74
|
+
allowed: false,
|
|
75
|
+
reason: trans('group:cannot_send_message'),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if (groupInfo?.status === GroupStatus.Muted && !isOwnerOrAdmin) {
|
|
79
|
+
return {
|
|
80
|
+
allowed: false,
|
|
81
|
+
reason: trans('group:messaging_muted_by_owner'),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return { allowed: true };
|
|
85
|
+
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { memo } from 'react';
|
|
1
|
+
import { memo, useCallback } 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';
|
|
5
5
|
import { useGetGroupInfo } from '../../hooks/conversation/useGetGroupInfo';
|
|
6
|
+
import { useLeaveGroup } from '../../hooks/groups/useLeaveGroup';
|
|
6
7
|
import { useConversation } from '../../store';
|
|
7
8
|
import ChatInfoProfile from './ChatInfoProfile';
|
|
8
9
|
import ChatInfoMediaRow from './ChatInfoMediaRow';
|
|
@@ -10,8 +11,8 @@ import ChatInfoDescriptionRow from './ChatInfoDescriptionRow';
|
|
|
10
11
|
import ChatInfoActions from './ChatInfoActions';
|
|
11
12
|
import type { ChatInfoProps } from './types';
|
|
12
13
|
import { trans } from '../../translation';
|
|
13
|
-
import { isGroupConversation } from '../../utils/conversation';
|
|
14
14
|
import { withChatDetailProvider } from '../../context';
|
|
15
|
+
import { useChatDetailContext } from '../../context';
|
|
15
16
|
|
|
16
17
|
const ChatInfo = memo(
|
|
17
18
|
({
|
|
@@ -30,10 +31,30 @@ const ChatInfo = memo(
|
|
|
30
31
|
const conversation = useConversation(conversationId);
|
|
31
32
|
const { data: target } = useGetConversationTarget(conversationId);
|
|
32
33
|
const isTargetActive = target ? target.isActive : true;
|
|
33
|
-
const isGroup =
|
|
34
|
+
const isGroup = useChatDetailContext().isGroupConversation;
|
|
35
|
+
|
|
34
36
|
const { data: groupInfo } = useGetGroupInfo(
|
|
35
37
|
isGroup ? conversation?.groupID : undefined
|
|
36
38
|
);
|
|
39
|
+
const { leaveGroup } = useLeaveGroup();
|
|
40
|
+
|
|
41
|
+
const handleLeaveGroup = useCallback(async () => {
|
|
42
|
+
if (!conversation?.groupID) return;
|
|
43
|
+
|
|
44
|
+
const didLeave = await leaveGroup(
|
|
45
|
+
conversation.groupID,
|
|
46
|
+
groupInfo?.visibility
|
|
47
|
+
);
|
|
48
|
+
if (didLeave) {
|
|
49
|
+
(onPressLeaveGroup ?? navigation.goBack)();
|
|
50
|
+
}
|
|
51
|
+
}, [
|
|
52
|
+
conversation?.groupID,
|
|
53
|
+
groupInfo?.visibility,
|
|
54
|
+
leaveGroup,
|
|
55
|
+
navigation.goBack,
|
|
56
|
+
onPressLeaveGroup,
|
|
57
|
+
]);
|
|
37
58
|
|
|
38
59
|
return (
|
|
39
60
|
<KContainer.Page flex edges={['bottom']} background="#EBEDF2">
|
|
@@ -56,10 +77,7 @@ const ChatInfo = memo(
|
|
|
56
77
|
/>
|
|
57
78
|
<ChatInfoMediaRow onPressMedia={onPressMedia} />
|
|
58
79
|
<KContainer.VisibleView visible={isGroup}>
|
|
59
|
-
<ChatInfoDescriptionRow
|
|
60
|
-
introduction={groupInfo?.introduction}
|
|
61
|
-
groupName={conversation?.showName}
|
|
62
|
-
/>
|
|
80
|
+
<ChatInfoDescriptionRow groupName={conversation?.showName} />
|
|
63
81
|
</KContainer.VisibleView>
|
|
64
82
|
<ChatInfoActions
|
|
65
83
|
conversation={conversation}
|
|
@@ -68,7 +86,7 @@ const ChatInfo = memo(
|
|
|
68
86
|
onPressOrders={onPressOrders}
|
|
69
87
|
onPressCreateGroup={onPressCreateGroup}
|
|
70
88
|
onPressAddToGroup={onPressAddToGroup}
|
|
71
|
-
onPressLeaveGroup={
|
|
89
|
+
onPressLeaveGroup={handleLeaveGroup}
|
|
72
90
|
onDismissGroup={onDismissGroup}
|
|
73
91
|
/>
|
|
74
92
|
</KContainer.ScrollView>
|
|
@@ -3,9 +3,9 @@ import { StyleSheet, type TextLayoutEvent } from 'react-native';
|
|
|
3
3
|
import { KContainer, KLabel, KColors } from '@droppii/libs';
|
|
4
4
|
import { trans } from '../../translation';
|
|
5
5
|
import GroupDescriptionModal from './GroupDescriptionModal';
|
|
6
|
+
import { useChatDetailContext } from '../../context';
|
|
6
7
|
|
|
7
8
|
interface ChatInfoDescriptionRowProps {
|
|
8
|
-
introduction?: string;
|
|
9
9
|
groupName?: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -13,7 +13,8 @@ const EXTRA_TEXT_LENGTH = 14; // Length of "...View More" text
|
|
|
13
13
|
const MAX_LINES = 2;
|
|
14
14
|
|
|
15
15
|
const ChatInfoDescriptionRow = memo(
|
|
16
|
-
({
|
|
16
|
+
({ groupName }: ChatInfoDescriptionRowProps) => {
|
|
17
|
+
const introduction = useChatDetailContext().groupInfo?.introduction;
|
|
17
18
|
const [modalVisible, setModalVisible] = useState(false);
|
|
18
19
|
const handlePressDescription = useCallback(() => setModalVisible(true), []);
|
|
19
20
|
const handleCloseDescription = useCallback(
|
|
@@ -63,11 +63,8 @@ import { ChatCurrentQuotedMessage } from './ChatCurrentQuotedMessage';
|
|
|
63
63
|
import { useSendMessage } from '../../hooks/message/useSendMessage';
|
|
64
64
|
import { openBotMenuSheet } from './BotMenuSheet';
|
|
65
65
|
import { UIUtils } from '../../utils/UIUtils';
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
68
|
-
useGuardedAction,
|
|
69
|
-
useHasGroupPermission,
|
|
70
|
-
} from '../../hooks/useGroupPermission';
|
|
66
|
+
import { PeerType } from '@droppii/openim-rn-client-sdk';
|
|
67
|
+
import { useCanSendMessage } from '../../hooks/useGroupPermission';
|
|
71
68
|
import { useIsJoinedGroup } from '../../hooks/useIsJoinedGroup';
|
|
72
69
|
|
|
73
70
|
interface ChatComposerInternalProps {
|
|
@@ -107,9 +104,8 @@ export const ChatComposer = memo(
|
|
|
107
104
|
const { mutateAsync: closeSupportSession } = useCloseSupportSession();
|
|
108
105
|
const { sendMessage } = useSendMessage();
|
|
109
106
|
const compose = useChatCompose();
|
|
110
|
-
const isSendMessageAllowed =
|
|
111
|
-
|
|
112
|
-
);
|
|
107
|
+
const { allowed: isSendMessageAllowed, reason: sendDeniedReason } =
|
|
108
|
+
useCanSendMessage();
|
|
113
109
|
|
|
114
110
|
const { data: isJoinedGroup } = useIsJoinedGroup(conversation?.groupID);
|
|
115
111
|
|
|
@@ -149,20 +145,22 @@ export const ChatComposer = memo(
|
|
|
149
145
|
onAttachmentOpenChange: setIsAttachmentOpen,
|
|
150
146
|
});
|
|
151
147
|
|
|
152
|
-
const handleSend =
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (__DEV__) console.error('[ChatComposer] Failed to send:', error);
|
|
160
|
-
}
|
|
161
|
-
}, [compose]),
|
|
162
|
-
{
|
|
163
|
-
deniedMessage: trans('chat:no_send_permission'),
|
|
148
|
+
const handleSend = useCallback(async () => {
|
|
149
|
+
if (!isSendMessageAllowed) {
|
|
150
|
+
UIUtils.toast.open({
|
|
151
|
+
title: sendDeniedReason,
|
|
152
|
+
theme: 'info',
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
164
155
|
}
|
|
165
|
-
|
|
156
|
+
|
|
157
|
+
if (compose.isUploading || !compose.canSend) return;
|
|
158
|
+
try {
|
|
159
|
+
await compose.send();
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (__DEV__) console.error('[ChatComposer] Failed to send:', error);
|
|
162
|
+
}
|
|
163
|
+
}, [compose, isSendMessageAllowed, sendDeniedReason]);
|
|
166
164
|
|
|
167
165
|
const handleInputFocus = useCallback(() => {
|
|
168
166
|
setIsInputFocused(true);
|
|
@@ -474,7 +472,7 @@ export const ChatComposer = memo(
|
|
|
474
472
|
);
|
|
475
473
|
} else if (!isSendMessageAllowed) {
|
|
476
474
|
content = (
|
|
477
|
-
<KContainer.View row alignItems justifyContent>
|
|
475
|
+
<KContainer.View row alignItems justifyContent paddingT={'1rem'}>
|
|
478
476
|
<KContainer.View marginR="0.25rem">
|
|
479
477
|
<KImage.VectorIcons
|
|
480
478
|
name="info-circle-o"
|
|
@@ -482,9 +480,7 @@ export const ChatComposer = memo(
|
|
|
482
480
|
color={KColors.palette.gray.w600}
|
|
483
481
|
/>
|
|
484
482
|
</KContainer.View>
|
|
485
|
-
<KLabel.Text typo="TextXsNormal">
|
|
486
|
-
{trans('group:cannot_send_message')}
|
|
487
|
-
</KLabel.Text>
|
|
483
|
+
<KLabel.Text typo="TextXsNormal">{sendDeniedReason}</KLabel.Text>
|
|
488
484
|
</KContainer.View>
|
|
489
485
|
);
|
|
490
486
|
} else {
|
|
@@ -11,9 +11,8 @@ import { NamePrefixIcon } from '../../components/ThreadCard/NamePrefixIcon';
|
|
|
11
11
|
import type { ChatDetailHeaderProps } from './types';
|
|
12
12
|
import { getConversationSubtitle } from './conversationHeader.utils';
|
|
13
13
|
import { useGetUsersInfo } from '../../hooks/users/useGetUsersInfo';
|
|
14
|
-
import { useGetGroupInfo } from '../../hooks/conversation/useGetGroupInfo';
|
|
15
|
-
import { isGroupConversation } from '../../utils/conversation';
|
|
16
14
|
import { useGetConversationDetail } from '../../hooks/conversation/useGetConversationDetail';
|
|
15
|
+
import { useChatDetailContext } from '../../context';
|
|
17
16
|
|
|
18
17
|
const ChatDetailHeader = memo(
|
|
19
18
|
({
|
|
@@ -34,11 +33,7 @@ const ChatDetailHeader = memo(
|
|
|
34
33
|
const { data: usersInfo } = useGetUsersInfo(
|
|
35
34
|
targetUserId ? [targetUserId] : []
|
|
36
35
|
);
|
|
37
|
-
|
|
38
|
-
const isGroup = isGroupConversation(conversation);
|
|
39
|
-
const { data: groupInfo } = useGetGroupInfo(
|
|
40
|
-
isGroup ? conversation?.groupID : undefined
|
|
41
|
-
);
|
|
36
|
+
const memberCount = useChatDetailContext().groupInfo?.memberCount;
|
|
42
37
|
|
|
43
38
|
const { faceURL, showName } = useMemo(() => {
|
|
44
39
|
const userInfo = usersInfo?.[0];
|
|
@@ -135,24 +130,14 @@ const ChatDetailHeader = memo(
|
|
|
135
130
|
color={KColors.palette.gray.w500}
|
|
136
131
|
numberOfLines={1}
|
|
137
132
|
>
|
|
138
|
-
{getConversationSubtitle(
|
|
139
|
-
conversation,
|
|
140
|
-
groupInfo?.memberCount
|
|
141
|
-
)}
|
|
133
|
+
{getConversationSubtitle(conversation, memberCount)}
|
|
142
134
|
</KLabel.Text>
|
|
143
135
|
)}
|
|
144
136
|
</KContainer.View>
|
|
145
137
|
</KContainer.Touchable>
|
|
146
138
|
</KContainer.View>
|
|
147
139
|
),
|
|
148
|
-
[
|
|
149
|
-
onBack,
|
|
150
|
-
onPressAvatar,
|
|
151
|
-
conversation,
|
|
152
|
-
faceURL,
|
|
153
|
-
showName,
|
|
154
|
-
groupInfo?.memberCount,
|
|
155
|
-
]
|
|
140
|
+
[onBack, onPressAvatar, conversation, faceURL, showName, memberCount]
|
|
156
141
|
);
|
|
157
142
|
|
|
158
143
|
return (
|
|
@@ -75,16 +75,31 @@ const i18nKey: Record<string, string> = {
|
|
|
75
75
|
'chat:group_created_by': '{{sender}} đã tạo nhóm',
|
|
76
76
|
'msg_type_group_name_updated': 'Đã đổi tên nhóm',
|
|
77
77
|
'chat:group_name_updated_by': '{{sender}} đã đổi tên nhóm',
|
|
78
|
+
'chat:group_name_updated_by_named':
|
|
79
|
+
'{{sender}} đã đổi tên nhóm thành {{newGroupName}}',
|
|
78
80
|
'msg_type_group_info_updated': 'Đã cập nhật thông tin nhóm',
|
|
79
81
|
'chat:group_info_updated_by': '{{sender}} đã cập nhật thông tin nhóm',
|
|
80
82
|
'msg_type_member_quit': 'Đã rời khỏi nhóm',
|
|
81
|
-
'chat:member_quit_by': '{{sender}} đã rời
|
|
83
|
+
'chat:member_quit_by': '{{sender}} đã rời nhóm',
|
|
82
84
|
'msg_type_group_owner_transferred': 'Đã chuyển quyền trưởng nhóm',
|
|
83
85
|
'chat:group_owner_transferred_by': '{{sender}} đã chuyển quyền trưởng nhóm',
|
|
86
|
+
'chat:group_owner_transferred_by_named':
|
|
87
|
+
'{{sender}} đã chuyển quyền trưởng nhóm cho {{memberName}}',
|
|
84
88
|
'msg_type_member_kicked': 'Đã xoá thành viên khỏi nhóm',
|
|
85
89
|
'chat:member_kicked_by': '{{sender}} đã xoá thành viên khỏi nhóm',
|
|
90
|
+
'chat:member_kicked_by_named': '{{sender}} đã xoá {{names}} khỏi nhóm',
|
|
86
91
|
'msg_type_member_invited': 'Đã thêm thành viên vào nhóm',
|
|
87
92
|
'chat:member_invited_by': '{{sender}} đã thêm thành viên vào nhóm',
|
|
93
|
+
'chat:member_invited_by_named': '{{sender}} đã thêm {{names}} vào nhóm',
|
|
94
|
+
'msg_type_group_member_set_admin': 'Đã chỉ định quản trị viên mới',
|
|
95
|
+
'chat:group_member_set_admin_by':
|
|
96
|
+
'{{sender}} đã đặt {{memberName}} làm quản trị viên',
|
|
97
|
+
'chat:group_member_set_admin_by_generic':
|
|
98
|
+
'{{sender}} đã đặt một thành viên làm quản trị viên',
|
|
99
|
+
'msg_type_message_pinned': 'Đã ghim một tin nhắn',
|
|
100
|
+
'chat:group_message_pinned_by': '{{sender}} đã ghim một tin nhắn',
|
|
101
|
+
'msg_type_message_unpinned': 'Đã bỏ ghim một tin nhắn',
|
|
102
|
+
'chat:group_message_unpinned_by': '{{sender}} đã bỏ ghim một tin nhắn',
|
|
88
103
|
'msg_type_member_enter': 'Đã tham gia nhóm',
|
|
89
104
|
'chat:member_enter_by': '{{sender}} đã tham gia nhóm',
|
|
90
105
|
'msg_type_group_dismissed': 'Nhóm đã bị giải tán',
|
|
@@ -124,6 +139,17 @@ const i18nKey: Record<string, string> = {
|
|
|
124
139
|
'chat:info_unpin_message': 'Bỏ ghim trò chuyện',
|
|
125
140
|
'chat:info_delete_history': 'Xoá lịch sử trò chuyện',
|
|
126
141
|
'chat:info_leave_group': 'Rời nhóm',
|
|
142
|
+
'chat:leave_group_confirm_title': 'Rời nhóm?',
|
|
143
|
+
'chat:leave_group_confirm_desc_private':
|
|
144
|
+
'Sau khi rời nhóm, bạn sẽ không thể xem tin nhắn từ cuộc trò chuyện này nữa. Bạn có chắc chắn muốn rời khỏi nhóm?',
|
|
145
|
+
'chat:leave_group_confirm_desc_public':
|
|
146
|
+
'Sau khi rời nhóm, bạn sẽ không thể nhắn tin hay nhận thông báo từ cuộc trò chuyện này nữa. Bạn có chắc chắn muốn rời khỏi nhóm?',
|
|
147
|
+
'chat:leave_group_confirm_primary': 'Rời nhóm',
|
|
148
|
+
'chat:leave_group_confirm_secondary': 'Ở lại nhóm',
|
|
149
|
+
'chat:leave_group_blocked_title': 'Không thể rời nhóm',
|
|
150
|
+
'chat:leave_group_blocked_desc':
|
|
151
|
+
'Bạn không thể rời nhóm khi đang là chủ nhóm, vui lòng chuyển quyền chủ nhóm cho thành viên khác và thử lại!',
|
|
152
|
+
'chat:leave_group_blocked_button': 'Đã hiểu',
|
|
127
153
|
'chat:dismiss_group': 'Giải tán nhóm',
|
|
128
154
|
'chat:info_view_more': 'Xem thêm',
|
|
129
155
|
'chat:group_description_title': 'Mô tả nhóm',
|
|
@@ -172,6 +198,8 @@ const i18nKey: Record<string, string> = {
|
|
|
172
198
|
'group:join': 'Tham gia',
|
|
173
199
|
'group:not_in_group': 'Bạn cần tham gia nhóm để gửi tin nhắn',
|
|
174
200
|
'group:cannot_send_message': 'Bạn không thể gửi tin nhắn vào nhóm này',
|
|
201
|
+
'group:messaging_muted_by_owner':
|
|
202
|
+
'Chỉ chủ nhóm và quản trị viên được phép gửi tin nhắn',
|
|
175
203
|
'group:joined_group': 'Đã tham gia nhóm {{groupName}}',
|
|
176
204
|
'group:public_group': 'Nhóm công khai',
|
|
177
205
|
'group:private_group': 'Nhóm riêng tư',
|
package/src/types/chat.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MessageType,
|
|
3
3
|
type ConversationItem,
|
|
4
|
+
type GroupItem,
|
|
4
5
|
type MessageItem,
|
|
5
6
|
type SelfUserInfo,
|
|
6
7
|
} from '@droppii/openim-rn-client-sdk';
|
|
@@ -206,6 +207,7 @@ export interface MessageStore {
|
|
|
206
207
|
export interface ChatDetailContextType {
|
|
207
208
|
conversationFlow?: IConversationFlow;
|
|
208
209
|
isBotConversation: boolean;
|
|
210
|
+
isGroupConversation: boolean;
|
|
209
211
|
setConverationFlowStep: (
|
|
210
212
|
step?: IConversationFlowStep,
|
|
211
213
|
isEnd?: boolean,
|
|
@@ -218,6 +220,7 @@ export interface ChatDetailContextType {
|
|
|
218
220
|
setQuotedMessage: (item?: MessageItem) => void;
|
|
219
221
|
conversationFlowMainMenu?: InputButtonItem[];
|
|
220
222
|
userInfo?: SelfUserInfo;
|
|
223
|
+
groupInfo?: GroupItem | null;
|
|
221
224
|
}
|
|
222
225
|
|
|
223
226
|
export interface ChatDetailProviderProps extends PropsWithChildren {}
|
|
@@ -28,8 +28,12 @@ export const getMessageText = (message: MessageItem): string => {
|
|
|
28
28
|
case MessageType.FaceMessage:
|
|
29
29
|
return '[Sticker]';
|
|
30
30
|
default:
|
|
31
|
+
// Never surface the raw notification payload as message text — it
|
|
32
|
+
// contains other users' userID/nickname/faceURL/permissions and would
|
|
33
|
+
// render verbatim as a normal chat bubble for any content type that
|
|
34
|
+
// isn't (yet) mapped in GROUP_NOTIFICATION_TEXT.
|
|
31
35
|
if (message.notificationElem?.detail) {
|
|
32
|
-
return
|
|
36
|
+
return '';
|
|
33
37
|
}
|
|
34
38
|
return message.content || '';
|
|
35
39
|
}
|