@droppii-org/chat-mobile 0.2.64 → 0.2.66

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 (68) hide show
  1. package/lib/module/context/ChatDetailContext.js +24 -3
  2. package/lib/module/context/ChatDetailContext.js.map +1 -1
  3. package/lib/module/core/useChatListener.js +21 -10
  4. package/lib/module/core/useChatListener.js.map +1 -1
  5. package/lib/module/hooks/conversation/useGetGroupInfo.js +19 -0
  6. package/lib/module/hooks/conversation/useGetGroupInfo.js.map +1 -0
  7. package/lib/module/hooks/message/useSendMessage.js +13 -3
  8. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  9. package/lib/module/hooks/query-keys.js +2 -1
  10. package/lib/module/hooks/query-keys.js.map +1 -1
  11. package/lib/module/screens/ChatInfo/ChatInfo.js +18 -2
  12. package/lib/module/screens/ChatInfo/ChatInfo.js.map +1 -1
  13. package/lib/module/screens/ChatInfo/ChatInfoActions.js +83 -55
  14. package/lib/module/screens/ChatInfo/ChatInfoActions.js.map +1 -1
  15. package/lib/module/screens/ChatInfo/ChatInfoDescriptionRow.js +77 -0
  16. package/lib/module/screens/ChatInfo/ChatInfoDescriptionRow.js.map +1 -0
  17. package/lib/module/screens/ChatInfo/GroupDescriptionModal.js +58 -0
  18. package/lib/module/screens/ChatInfo/GroupDescriptionModal.js.map +1 -0
  19. package/lib/module/screens/chat-detail/ChatComposer.js +12 -1
  20. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  21. package/lib/module/translation/resources/i18n.js +5 -1
  22. package/lib/module/translation/resources/i18n.js.map +1 -1
  23. package/lib/module/types/chat.js.map +1 -1
  24. package/lib/module/types/events.js +1 -0
  25. package/lib/module/types/events.js.map +1 -1
  26. package/lib/module/utils/conversation.js +3 -0
  27. package/lib/module/utils/conversation.js.map +1 -1
  28. package/lib/typescript/src/context/ChatDetailContext.d.ts.map +1 -1
  29. package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
  30. package/lib/typescript/src/hooks/conversation/useGetGroupInfo.d.ts +2 -0
  31. package/lib/typescript/src/hooks/conversation/useGetGroupInfo.d.ts.map +1 -0
  32. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  33. package/lib/typescript/src/hooks/query-keys.d.ts +1 -0
  34. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  35. package/lib/typescript/src/screens/ChatInfo/ChatInfo.d.ts +1 -1
  36. package/lib/typescript/src/screens/ChatInfo/ChatInfo.d.ts.map +1 -1
  37. package/lib/typescript/src/screens/ChatInfo/ChatInfoActions.d.ts +3 -1
  38. package/lib/typescript/src/screens/ChatInfo/ChatInfoActions.d.ts.map +1 -1
  39. package/lib/typescript/src/screens/ChatInfo/ChatInfoDescriptionRow.d.ts +7 -0
  40. package/lib/typescript/src/screens/ChatInfo/ChatInfoDescriptionRow.d.ts.map +1 -0
  41. package/lib/typescript/src/screens/ChatInfo/GroupDescriptionModal.d.ts +9 -0
  42. package/lib/typescript/src/screens/ChatInfo/GroupDescriptionModal.d.ts.map +1 -0
  43. package/lib/typescript/src/screens/ChatInfo/types.d.ts +1 -0
  44. package/lib/typescript/src/screens/ChatInfo/types.d.ts.map +1 -1
  45. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  46. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  47. package/lib/typescript/src/types/chat.d.ts +2 -1
  48. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  49. package/lib/typescript/src/types/events.d.ts +6 -1
  50. package/lib/typescript/src/types/events.d.ts.map +1 -1
  51. package/lib/typescript/src/utils/conversation.d.ts +1 -0
  52. package/lib/typescript/src/utils/conversation.d.ts.map +1 -1
  53. package/package.json +2 -2
  54. package/src/context/ChatDetailContext.tsx +34 -1
  55. package/src/core/useChatListener.ts +53 -17
  56. package/src/hooks/conversation/useGetGroupInfo.ts +18 -0
  57. package/src/hooks/message/useSendMessage.ts +21 -2
  58. package/src/hooks/query-keys.ts +1 -0
  59. package/src/screens/ChatInfo/ChatInfo.tsx +16 -0
  60. package/src/screens/ChatInfo/ChatInfoActions.tsx +68 -35
  61. package/src/screens/ChatInfo/ChatInfoDescriptionRow.tsx +102 -0
  62. package/src/screens/ChatInfo/GroupDescriptionModal.tsx +67 -0
  63. package/src/screens/ChatInfo/types.ts +1 -0
  64. package/src/screens/chat-detail/ChatComposer.tsx +17 -1
  65. package/src/translation/resources/i18n.ts +4 -0
  66. package/src/types/chat.ts +2 -0
  67. package/src/types/events.ts +7 -0
  68. package/src/utils/conversation.ts +6 -0
@@ -13,18 +13,22 @@ import { trans } from '../../translation';
13
13
  interface ChatInfoActionsProps {
14
14
  conversation?: ConversationItem;
15
15
  isTargetActive: boolean;
16
+ isGroup?: boolean;
16
17
  onPressOrders?: () => void;
17
18
  onPressCreateGroup?: () => void;
18
19
  onPressAddToGroup?: () => void;
20
+ onPressLeaveGroup?: () => void;
19
21
  }
20
22
 
21
23
  const ChatInfoActions = memo(
22
24
  ({
23
25
  conversation,
24
26
  isTargetActive,
27
+ isGroup,
25
28
  onPressOrders,
26
29
  onPressCreateGroup,
27
30
  onPressAddToGroup,
31
+ onPressLeaveGroup,
28
32
  }: ChatInfoActionsProps) => {
29
33
  const { pinConversation, unpinConversation } = usePinConversation();
30
34
  const isPinned = Boolean(conversation?.isPinned);
@@ -41,8 +45,10 @@ const ChatInfoActions = memo(
41
45
  const handleDeleteHistory = useCallback(() => {}, []);
42
46
 
43
47
  const primaryData = useMemo(() => {
44
- const items: KListItemBaseItemProps[] = [
45
- {
48
+ const items: KListItemBaseItemProps[] = [];
49
+
50
+ if (!isGroup) {
51
+ items.push({
46
52
  leftNode: { icon: { vectorName: 'receipt-o' } },
47
53
  onPress: onPressOrders,
48
54
  paddingH: '0.75rem',
@@ -52,34 +58,34 @@ const ChatInfoActions = memo(
52
58
  {trans('chat:info_orders', { name: showName })}
53
59
  </KLabel.Text>
54
60
  ),
55
- },
56
- ];
61
+ });
57
62
 
58
- if (isTargetActive) {
59
- items.push(
60
- {
61
- leftNode: { icon: { vectorName: 'user-three-o' } },
62
- onPress: onPressCreateGroup,
63
- paddingH: '0.75rem',
64
- paddingV: '0.75rem',
65
- customLabel: (
66
- <KLabel.Text typo="TextMdMedium" numberOfLines={1}>
67
- {trans('chat:info_create_group', { name: showName })}
68
- </KLabel.Text>
69
- ),
70
- },
71
- {
72
- leftNode: { icon: { vectorName: 'user-add-o' } },
73
- onPress: onPressAddToGroup,
74
- paddingH: '0.75rem',
75
- paddingV: '0.75rem',
76
- customLabel: (
77
- <KLabel.Text typo="TextMdMedium" numberOfLines={1}>
78
- {trans('chat:info_add_to_group')}
79
- </KLabel.Text>
80
- ),
81
- }
82
- );
63
+ if (isTargetActive) {
64
+ items.push(
65
+ {
66
+ leftNode: { icon: { vectorName: 'user-three-o' } },
67
+ onPress: onPressCreateGroup,
68
+ paddingH: '0.75rem',
69
+ paddingV: '0.75rem',
70
+ customLabel: (
71
+ <KLabel.Text typo="TextMdMedium" numberOfLines={1}>
72
+ {trans('chat:info_create_group', { name: showName })}
73
+ </KLabel.Text>
74
+ ),
75
+ },
76
+ {
77
+ leftNode: { icon: { vectorName: 'user-add-o' } },
78
+ onPress: onPressAddToGroup,
79
+ paddingH: '0.75rem',
80
+ paddingV: '0.75rem',
81
+ customLabel: (
82
+ <KLabel.Text typo="TextMdMedium" numberOfLines={1}>
83
+ {trans('chat:info_add_to_group')}
84
+ </KLabel.Text>
85
+ ),
86
+ }
87
+ );
88
+ }
83
89
  }
84
90
 
85
91
  items.push({
@@ -102,6 +108,7 @@ const ChatInfoActions = memo(
102
108
  return items;
103
109
  }, [
104
110
  showName,
111
+ isGroup,
105
112
  isTargetActive,
106
113
  isPinned,
107
114
  onPressOrders,
@@ -110,8 +117,35 @@ const ChatInfoActions = memo(
110
117
  handlePinPress,
111
118
  ]);
112
119
 
113
- const deleteData: KListItemBaseItemProps[] = useMemo(
114
- () => [
120
+ const dangerData: KListItemBaseItemProps[] = useMemo(() => {
121
+ if (isGroup) {
122
+ return [
123
+ {
124
+ leftNode: {
125
+ icon: {
126
+ vectorName: 'logout-o',
127
+ tintColor: KColors.danger.normal,
128
+ },
129
+ },
130
+ tintColor: KColors.danger.normal,
131
+ onPress: onPressLeaveGroup,
132
+ showChevron: false,
133
+ paddingH: '0.75rem',
134
+ paddingV: '0.75rem',
135
+ customLabel: (
136
+ <KLabel.Text
137
+ typo="TextMdMedium"
138
+ color={KColors.danger.normal}
139
+ numberOfLines={1}
140
+ >
141
+ {trans('chat:info_leave_group')}
142
+ </KLabel.Text>
143
+ ),
144
+ },
145
+ ];
146
+ }
147
+
148
+ return [
115
149
  {
116
150
  label: trans('chat:info_delete_history'),
117
151
  leftNode: {
@@ -135,9 +169,8 @@ const ChatInfoActions = memo(
135
169
  </KLabel.Text>
136
170
  ),
137
171
  },
138
- ],
139
- [handleDeleteHistory]
140
- );
172
+ ];
173
+ }, [isGroup, onPressLeaveGroup, handleDeleteHistory]);
141
174
 
142
175
  return (
143
176
  <>
@@ -158,7 +191,7 @@ const ChatInfoActions = memo(
158
191
  marginT="0.75rem"
159
192
  hiddenOverflow
160
193
  >
161
- <KListItem.Base data={deleteData} />
194
+ <KListItem.Base data={dangerData} />
162
195
  </KContainer.View>
163
196
  </>
164
197
  );
@@ -0,0 +1,102 @@
1
+ import { memo, useCallback, useState } from 'react';
2
+ import { StyleSheet, type TextLayoutEvent } from 'react-native';
3
+ import { KContainer, KLabel, KColors } from '@droppii/libs';
4
+ import { trans } from '../../translation';
5
+ import GroupDescriptionModal from './GroupDescriptionModal';
6
+
7
+ interface ChatInfoDescriptionRowProps {
8
+ introduction?: string;
9
+ groupName?: string;
10
+ }
11
+
12
+ const EXTRA_TEXT_LENGTH = 14; // Length of "...View More" text
13
+ const MAX_LINES = 2;
14
+
15
+ const ChatInfoDescriptionRow = memo(
16
+ ({ introduction, groupName }: ChatInfoDescriptionRowProps) => {
17
+ const [modalVisible, setModalVisible] = useState(false);
18
+ const handlePressDescription = useCallback(() => setModalVisible(true), []);
19
+ const handleCloseDescription = useCallback(
20
+ () => setModalVisible(false),
21
+ []
22
+ );
23
+
24
+ const [isTruncated, setIsTruncated] = useState(false);
25
+ const [displayedText, setDisplayedText] = useState(introduction);
26
+
27
+ const handleTextLayout = useCallback((event: TextLayoutEvent) => {
28
+ const isTextTruncated = event.nativeEvent.lines.length > MAX_LINES;
29
+
30
+ setIsTruncated(isTextTruncated);
31
+
32
+ if (isTextTruncated) {
33
+ setDisplayedText(
34
+ event.nativeEvent.lines
35
+ .slice(0, MAX_LINES)
36
+ .map((line) => line.text)
37
+ .join(' ')
38
+ .slice(0, -EXTRA_TEXT_LENGTH) + '...'
39
+ );
40
+ }
41
+ }, []);
42
+
43
+ const textProps = {
44
+ color: KColors.gray.dark,
45
+ typo: 'TextMdMedium' as const,
46
+ paddingH: '0.75rem' as const,
47
+ };
48
+
49
+ if (!introduction) {
50
+ return null;
51
+ }
52
+
53
+ return (
54
+ <>
55
+ <KContainer.View
56
+ background={KColors.white}
57
+ br="3x"
58
+ marginH="0.75rem"
59
+ marginT="0.75rem"
60
+ paddingV="0.75rem"
61
+ >
62
+ <KLabel.Text
63
+ {...textProps}
64
+ style={styles.hiddenText}
65
+ numberOfLines={MAX_LINES + 1}
66
+ onTextLayout={handleTextLayout}
67
+ >
68
+ {introduction}
69
+ </KLabel.Text>
70
+ <KLabel.Text {...textProps} numberOfLines={2}>
71
+ {displayedText}
72
+ <KContainer.VisibleView visible={isTruncated}>
73
+ <KLabel.Text
74
+ {...textProps}
75
+ color={KColors.primary.normal}
76
+ onPress={handlePressDescription}
77
+ >
78
+ {trans('chat:info_view_more')}
79
+ </KLabel.Text>
80
+ </KContainer.VisibleView>
81
+ </KLabel.Text>
82
+ </KContainer.View>
83
+ <GroupDescriptionModal
84
+ visible={modalVisible}
85
+ groupName={groupName}
86
+ introduction={introduction}
87
+ onClose={handleCloseDescription}
88
+ />
89
+ </>
90
+ );
91
+ }
92
+ );
93
+
94
+ export default ChatInfoDescriptionRow;
95
+
96
+ const styles = StyleSheet.create({
97
+ hiddenText: {
98
+ position: 'absolute',
99
+ opacity: 0,
100
+ zIndex: -1,
101
+ },
102
+ });
@@ -0,0 +1,67 @@
1
+ import { memo } from 'react';
2
+ import { Modal, StyleSheet } from 'react-native';
3
+ import { KContainer, KNavigation, KLabel, KColors } from '@droppii/libs';
4
+ import { trans } from '../../translation';
5
+ import { SafeAreaProvider } from 'react-native-safe-area-context';
6
+
7
+ interface GroupDescriptionModalProps {
8
+ visible: boolean;
9
+ groupName?: string;
10
+ introduction?: string;
11
+ onClose: () => void;
12
+ }
13
+
14
+ const GroupDescriptionModal = memo(
15
+ ({
16
+ visible,
17
+ groupName,
18
+ introduction,
19
+ onClose,
20
+ }: GroupDescriptionModalProps) => (
21
+ <Modal
22
+ visible={visible}
23
+ transparent={false}
24
+ statusBarTranslucent
25
+ style={styles.modal}
26
+ presentationStyle="fullScreen"
27
+ animationType="slide"
28
+ onRequestClose={onClose}
29
+ >
30
+ <SafeAreaProvider>
31
+ <KContainer.Page flex edges={['bottom']} background={KColors.white}>
32
+ <KNavigation.Header
33
+ theme="light"
34
+ alignment="left"
35
+ size="small"
36
+ leftButton={{
37
+ id: 'GroupDescriptionModalCloseButton',
38
+ icon: { vectorName: 'close-o' },
39
+ onPress: onClose,
40
+ }}
41
+ title={trans('chat:group_description_title')}
42
+ subTitle={groupName}
43
+ shadow
44
+ />
45
+ <KLabel.Text
46
+ paddingH="1rem"
47
+ paddingT="0.75rem"
48
+ typo="TextMdNormal"
49
+ color={KColors.gray.dark}
50
+ >
51
+ {introduction}
52
+ </KLabel.Text>
53
+ </KContainer.Page>
54
+ </SafeAreaProvider>
55
+ </Modal>
56
+ )
57
+ );
58
+
59
+ GroupDescriptionModal.displayName = 'GroupDescriptionModal';
60
+
61
+ export default GroupDescriptionModal;
62
+
63
+ const styles = StyleSheet.create({
64
+ modal: {
65
+ flex: 1,
66
+ },
67
+ });
@@ -8,4 +8,5 @@ export interface ChatInfoProps {
8
8
  onPressOrders?: () => void;
9
9
  onPressCreateGroup?: () => void;
10
10
  onPressAddToGroup?: () => void;
11
+ onPressLeaveGroup?: () => void;
11
12
  }
@@ -63,6 +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 { GroupPermission, SessionType } from '@droppii/openim-rn-client-sdk';
67
+ import { useConversationStore } from '../../store';
66
68
 
67
69
  interface ChatComposerInternalProps {
68
70
  isEmptyMessage?: boolean;
@@ -95,6 +97,7 @@ export const ChatComposer = memo(
95
97
  const { mutateAsync: closeSupportSession } = useCloseSupportSession();
96
98
  const { sendMessage } = useSendMessage();
97
99
  const compose = useChatCompose();
100
+ const groupPermissions = useChatDetailContext().groupPermissions;
98
101
 
99
102
  const {
100
103
  activePanelType,
@@ -129,13 +132,26 @@ export const ChatComposer = memo(
129
132
  });
130
133
 
131
134
  const handleSend = useCallback(async () => {
135
+ const currentConversation = useConversationStore
136
+ .getState()
137
+ .getCurrentConversation();
138
+ if (
139
+ currentConversation?.conversationType === SessionType.Group &&
140
+ !groupPermissions?.includes(GroupPermission.SendMessage)
141
+ ) {
142
+ UIUtils.toast.open({
143
+ title: trans('chat:no_send_permission'),
144
+ theme: 'info',
145
+ });
146
+ return;
147
+ }
132
148
  if (compose.isUploading || !compose.canSend) return;
133
149
  try {
134
150
  await compose.send();
135
151
  } catch (error) {
136
152
  if (__DEV__) console.error('[ChatComposer] Failed to send:', error);
137
153
  }
138
- }, [compose]);
154
+ }, [compose, groupPermissions]);
139
155
 
140
156
  const handleInputFocus = useCallback(() => {
141
157
  setIsInputFocused(true);
@@ -92,6 +92,9 @@ const i18nKey: Record<string, string> = {
92
92
  'chat:info_pin_message': 'Ghim trò chuyện',
93
93
  'chat:info_unpin_message': 'Bỏ ghim trò chuyện',
94
94
  'chat:info_delete_history': 'Xoá lịch sử trò chuyện',
95
+ 'chat:info_leave_group': 'Rời nhóm',
96
+ 'chat:info_view_more': 'Xem thêm',
97
+ 'chat:group_description_title': 'Mô tả nhóm',
95
98
  'start': 'Bắt đầu',
96
99
  'restart': 'Bắt đầu lại',
97
100
  'gallery': 'Ảnh/Video',
@@ -129,6 +132,7 @@ const i18nKey: Record<string, string> = {
129
132
  'search_conversation:empty_hint': 'Bạn có thể tìm kiếm theo liên hệ',
130
133
  'view_all': 'Xem tất cả',
131
134
  'search_message:revoked_message': 'Tin nhắn đã được thu hồi',
135
+ 'chat:no_send_permission': 'Bạn không có quyền gửi tin nhắn',
132
136
  };
133
137
 
134
138
  export default {
package/src/types/chat.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ GroupPermission,
2
3
  MessageType,
3
4
  type ConversationItem,
4
5
  type MessageItem,
@@ -216,6 +217,7 @@ export interface ChatDetailContextType {
216
217
  quotedMessage?: MessageItem;
217
218
  setQuotedMessage: (item?: MessageItem) => void;
218
219
  conversationFlowMainMenu?: InputButtonItem[];
220
+ groupPermissions: GroupPermission[];
219
221
  }
220
222
 
221
223
  export interface ChatDetailProviderProps extends PropsWithChildren {}
@@ -1,7 +1,10 @@
1
+ import type { GroupPermission } from '@droppii/openim-rn-client-sdk';
2
+
1
3
  export enum ChatEvent {
2
4
  ScrollToMessage = 'chat:scroll_to_message',
3
5
  ScrollToBottom = 'chat:scroll_to_bottom',
4
6
  MessageRevoked = 'chat:message_revoked',
7
+ GroupPermissionUpdated = 'chat:group_permission_updated',
5
8
  }
6
9
 
7
10
  export interface ScrollToMessagePayload {
@@ -11,3 +14,7 @@ export interface ScrollToMessagePayload {
11
14
  export interface MessageRevokedPayload {
12
15
  clientMsgID: string;
13
16
  }
17
+
18
+ export interface GroupPermissionUpdatedPayload {
19
+ permissions: GroupPermission[];
20
+ }
@@ -56,3 +56,9 @@ export const getBotId = (conversation?: ConversationItem) => {
56
56
  }
57
57
  return undefined;
58
58
  };
59
+
60
+ export const isGroupConversation = (
61
+ conversation?: Pick<ConversationItem, 'conversationType' | 'peerType'>
62
+ ) => {
63
+ return conversation?.peerType === PeerType.Group;
64
+ };