@smart-link/rn-im 1.0.13 → 1.0.18

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.
@@ -83,7 +83,8 @@ const ChatAvatarLocal = ({ fileId, size, name, defaultAvatar }) => {
83
83
  (0, react_2.useEffect)(() => {
84
84
  loadAvatar().then(_r => { });
85
85
  }, [fileId]);
86
- if (ready && avatar) {
86
+ let hasCorrectAvatar = avatar ? avatar.indexOf("com.caih") > -1 || avatar.indexOf("http") > -1 : false;
87
+ if (ready && hasCorrectAvatar) {
87
88
  return (<LocalImage_1.default localPath={avatar} style={size ? { width: size, height: size, borderRadius: size / 2 } : { width: 40, height: 40, borderRadius: 20 }}/>);
88
89
  }
89
90
  else if (ready) {
@@ -35,6 +35,7 @@ const MaterialIcons_1 = __importDefault(require("react-native-vector-icons/Mater
35
35
  const ChatAvatar_1 = __importDefault(require("../../../components/ChatAvatar/ChatAvatar"));
36
36
  const useFormatMsgTime_1 = __importDefault(require("../../../hooks/useFormatMsgTime"));
37
37
  const useTranslation_1 = __importDefault(require("../../../hooks/useTranslation"));
38
+ const init_1 = require("../../../init");
38
39
  const ConversationCard = (0, react_1.memo)(props => {
39
40
  const { status, selected, conversation, onPressCard, onLongPressCard } = props;
40
41
  const { t } = (0, useTranslation_1.default)();
@@ -45,6 +46,12 @@ const ConversationCard = (0, react_1.memo)(props => {
45
46
  const content = (0, im_base_1.getConversationContent)(text, errorText, draftText, tipsList, countText, t);
46
47
  const contentStyles = content.tips ? styles.contentRedStyles : null;
47
48
  const formatTime = (0, useFormatMsgTime_1.default)(time, true);
49
+ const imManager = (0, init_1.getImManager)();
50
+ // 还没找到为啥会让空的头像的人变成登录人的头像,先解决表面问题
51
+ let newAvatars = avatars;
52
+ if (avatars === imManager.user.avatars && name !== imManager.user.userName) {
53
+ newAvatars = "";
54
+ }
48
55
  return (<react_native_1.TouchableOpacity activeOpacity={0.5} style={[styles.card, enableTopStyles, selected && styles.selected]} onPress={() => {
49
56
  onPressCard && onPressCard(conversation);
50
57
  }} onLongPress={({ nativeEvent }) => {
@@ -52,7 +59,7 @@ const ConversationCard = (0, react_1.memo)(props => {
52
59
  }}>
53
60
  <react_native_1.View style={{ height: (0, rn_ui_1.dp)(50), width: (0, rn_ui_1.dp)(50), marginRight: (0, rn_ui_1.dp)(16) }}>
54
61
  <rn_ui_1.Badge text={unreadCount} dot={showPoint}>
55
- <ChatAvatar_1.default disabled type={type} id={id} size={(0, rn_ui_1.dp)(48)} name={name} url={avatars}/>
62
+ <ChatAvatar_1.default disabled type={type} id={id} size={(0, rn_ui_1.dp)(48)} name={name} url={newAvatars}/>
56
63
  </rn_ui_1.Badge>
57
64
  {status === 'online' && <react_native_1.View style={styles.status}>
58
65
  <MaterialIcons_1.default size={(0, rn_ui_1.dp)(14)} color={status === 'online' ? '#4caf50' : '#616161'} name={status === 'online' ? 'check-circle' : 'not-interested'}/>
@@ -62,12 +62,23 @@ const OptionGroupMoreMember = (props) => {
62
62
  </react_native_1.TouchableOpacity>);
63
63
  };
64
64
  const list = (0, react_1.useMemo)(() => {
65
- return allMembers.slice().reverse().filter((item) => {
65
+ const allMembersData = allMembers.slice().reverse().filter((item) => {
66
66
  if (keyword) {
67
67
  return item.memberName.indexOf(keyword) > -1;
68
68
  }
69
69
  return true;
70
70
  });
71
+ // 让群主在第一个
72
+ let dealData = allMembersData;
73
+ const ownerIndex = dealData.findIndex(m => m.memberLevel === "owner");
74
+ if (ownerIndex > 0) {
75
+ dealData = [
76
+ allMembersData[ownerIndex],
77
+ ...allMembersData.slice(0, ownerIndex),
78
+ ...allMembersData.slice(ownerIndex + 1) // 群主之后的成员
79
+ ];
80
+ }
81
+ return dealData;
71
82
  }, [allMembers, keyword]);
72
83
  return (<rn_ui_1.NavigationPage noPadding>
73
84
  <rn_ui_1.SearchInput value={keyword} onChange={(txt) => {
@@ -22,7 +22,7 @@ const { LIAISON } = im_base_1.GroupType;
22
22
  // 最大显示人数
23
23
  const MAX_SHOW_MEMBER_COUNT = 23;
24
24
  const ConversationSetting = ({ navigation }) => {
25
- var _a;
25
+ var _a, _b;
26
26
  const { t } = (0, useTranslation_1.default)();
27
27
  const { currentConversation } = (0, useImSelector_1.useConversation)();
28
28
  const { groupDetail } = (0, useImSelector_1.useChatGroup)();
@@ -136,7 +136,7 @@ const ConversationSetting = ({ navigation }) => {
136
136
  </>);
137
137
  break;
138
138
  case im_base_1.ConversationType.C2G:
139
- const avatarList = groupMemberList
139
+ let avatarList = groupMemberList
140
140
  .map(item => {
141
141
  var _a;
142
142
  return {
@@ -147,6 +147,23 @@ const ConversationSetting = ({ navigation }) => {
147
147
  };
148
148
  })
149
149
  .splice(0, MAX_SHOW_MEMBER_COUNT);
150
+ let fMaster = groupMemberList.find(item => item.memberLevel === OWNER);
151
+ // 假设 fMaster 是要插入的对象,且它有一个唯一标识,比如 userId
152
+ if (fMaster) {
153
+ const fMasterWithRequiredFields = {
154
+ userId: fMaster.userId,
155
+ userName: (_b = fMaster.memberName) !== null && _b !== void 0 ? _b : '',
156
+ avatars: fMaster.memberAvatars,
157
+ memberLevel: fMaster.memberLevel,
158
+ };
159
+ // 先移除已存在的群主
160
+ const existingIndex = avatarList.findIndex(item => item.userId === (fMaster === null || fMaster === void 0 ? void 0 : fMaster.userId));
161
+ if (existingIndex > -1) {
162
+ avatarList.splice(existingIndex, 1);
163
+ }
164
+ // 插入群主
165
+ avatarList.unshift(fMasterWithRequiredFields);
166
+ }
150
167
  content = (<>
151
168
  <OptionAvatars_1.default list={avatarList} showTitle memberTotalCount={memberTotalCount} showAdd showMore={isMgr ? avatarList.length > 8 : avatarList.length > 9} isMgr={isMgr} addUser={() => {
152
169
  // 添加成员
@@ -80,6 +80,10 @@ const SettingChatBg = ({ navigation }) => {
80
80
  id: currentConversation.id,
81
81
  imageBackground: fileId,
82
82
  }));
83
+ yield imManager.db.updateConversationById({
84
+ id: currentConversation.id,
85
+ imageBackground: fileId,
86
+ });
83
87
  // 更新本地资源存储
84
88
  yield imManager.updateResource({
85
89
  url: fileId,
@@ -55,6 +55,7 @@ const MessageList = ({ navigation }) => {
55
55
  const [targetMessageSeq, setTargetMessageSeq] = (0, react_1.useState)('');
56
56
  const [selectMessage, setSelectMessage] = (0, react_1.useState)();
57
57
  const [nativeEvent, setNativeEvent] = (0, react_1.useState)();
58
+ const [loading, setLoading] = (0, react_1.useState)(false);
58
59
  const isB2C = (currentConversation === null || currentConversation === void 0 ? void 0 : currentConversation.type) === im_base_1.ConversationType.B2C;
59
60
  (0, react_1.useEffect)(() => {
60
61
  console.log('[进入会话]: ', currentConversation === null || currentConversation === void 0 ? void 0 : currentConversation.name);
@@ -162,7 +163,9 @@ const MessageList = ({ navigation }) => {
162
163
 
163
164
  {tips && tips.type === im_base_1.TipsType.AT && (<MessageItemTips_1.default {...tips}/>)}
164
165
  <VideoPlayer_1.default />
165
- <MessageOption_1.default visible={Boolean(selectMessage)} nativeEvent={nativeEvent} message={selectMessage} onClose={() => {
166
+ <MessageOption_1.default visible={Boolean(selectMessage)} nativeEvent={nativeEvent} message={selectMessage} setLoading={(value) => {
167
+ setLoading(value);
168
+ }} onClose={() => {
166
169
  setNativeEvent(undefined);
167
170
  setSelectMessage(undefined);
168
171
  }}/>
@@ -170,6 +173,13 @@ const MessageList = ({ navigation }) => {
170
173
  <MessagePictureAlbum_1.default visible={showAlbum} messages={messages} messageSeq={targetMessageSeq} onClose={() => {
171
174
  setShowAlbum(false);
172
175
  }}/>
176
+
177
+ {loading && (<react_native_1.View style={styles.loadingContainer}>
178
+ <react_native_1.ActivityIndicator size="large" color="#FFFFFF"/>
179
+ </react_native_1.View>)}
173
180
  </rn_ui_1.NavigationPage>);
174
181
  };
182
+ const styles = react_native_1.StyleSheet.create({
183
+ loadingContainer: Object.assign(Object.assign({}, react_native_1.StyleSheet.absoluteFillObject), { backgroundColor: 'rgba(0, 0, 0, 0.5)', justifyContent: 'center', alignItems: 'center', zIndex: 9999 }),
184
+ });
175
185
  exports.default = MessageList;
@@ -51,7 +51,7 @@ const MessageItem = ({ isMultiple, multipleSelect, conversation, message, mine,
51
51
  const isC2C = (conversation === null || conversation === void 0 ? void 0 : conversation.type) === im_base_1.ConversationType.C2C;
52
52
  const isMultipleChecked = !!(multipleSelect === null || multipleSelect === void 0 ? void 0 : multipleSelect[message.messageSeq]);
53
53
  const notArrow = [im_base_1.PayloadType.PICTURE, im_base_1.PayloadType.VIDEO].includes(message.payloadType);
54
- const isUndo = message.messageStatus === im_base_1.MessageStatus.UNDO;
54
+ const isUndo = message.messageStatus === im_base_1.MessageStatus.UNDO || message.payloadType === im_base_1.PayloadType.CONVERSATION_MESSAGE_UNDO;
55
55
  const isMessagePayload = !isB2C && !isNotify && !isUndo;
56
56
  return (<react_native_1.View style={styles.root} onLayout={(e) => {
57
57
  onLayoutItem(message, e);
@@ -6,6 +6,7 @@ type MessageOptionProps = {
6
6
  visible: boolean;
7
7
  nativeEvent?: NativeTouchEvent;
8
8
  onClose: () => void;
9
+ setLoading: (loading: boolean) => void;
9
10
  };
10
- declare const _default: React.MemoExoticComponent<({ visible, message, nativeEvent, onClose }: MessageOptionProps) => React.JSX.Element | null>;
11
+ declare const _default: React.MemoExoticComponent<({ visible, message, nativeEvent, onClose, setLoading }: MessageOptionProps) => React.JSX.Element | null>;
11
12
  export default _default;
@@ -52,7 +52,7 @@ const elements_1 = require("@react-navigation/elements");
52
52
  const useImSelector_1 = require("../../../hooks/useImSelector");
53
53
  const file_1 = require("../../../utils/file");
54
54
  const ReceiptBack_1 = require("../../../pages/message/components/ReceiptBack");
55
- const getMessageAction = (t, message, navigation) => {
55
+ const getMessageAction = (t, message, navigation, setLoading) => {
56
56
  if (!message)
57
57
  return [];
58
58
  const actions = (0, im_base_1.getMessageActionKeys)(message.payloadType);
@@ -120,9 +120,12 @@ const getMessageAction = (t, message, navigation) => {
120
120
  onPress: () => __awaiter(void 0, void 0, void 0, function* () {
121
121
  (0, common_action_sheet_1.ConfirmActionSheet)(t('confirmUndoMessage'), () => __awaiter(void 0, void 0, void 0, function* () {
122
122
  try {
123
+ setLoading(true);
123
124
  yield (0, init_1.getImManager)().undoMessage(message);
125
+ setLoading(false);
124
126
  }
125
127
  catch (e) {
128
+ setLoading(false);
126
129
  rn_ui_1.Toast.error(t('undoFail'));
127
130
  }
128
131
  }), t);
@@ -219,13 +222,13 @@ const renderOperations = (items, close) => {
219
222
  })}
220
223
  </react_native_1.View>);
221
224
  };
222
- const MessageOption = ({ visible, message, nativeEvent, onClose }) => {
225
+ const MessageOption = ({ visible, message, nativeEvent, onClose, setLoading }) => {
223
226
  var _a;
224
227
  const { t } = (0, useTranslation_1.default)();
225
228
  const headerHeight = (0, elements_1.useHeaderHeight)();
226
229
  const { messageBarHeight } = (0, useImSelector_1.usePanel)();
227
230
  const navigation = (0, rn_ui_1.useNavigation)();
228
- const items = getMessageAction(t, message, navigation);
231
+ const items = getMessageAction(t, message, navigation, setLoading);
229
232
  const translateX = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
230
233
  const translateY = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
231
234
  const scale = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
@@ -72,7 +72,7 @@ const MessagePictureAlbum = ({ visible, onClose, messages, messageSeq }) => {
72
72
  {visible && (<react_native_1.View style={styles.root}>
73
73
  {react_native_1.Platform.OS === 'android' && <react_native_1.StatusBar backgroundColor={'#000'}/>}
74
74
  <react_native_1.View style={{ width: width, height: height }}>
75
- <rn_ui_1.AlbumView images={sources} defaultIndex={index}/>
75
+ <rn_ui_1.AlbumView images={sources} defaultIndex={index} onClose={onClose}/>
76
76
  </react_native_1.View>
77
77
 
78
78
  <react_native_1.TouchableOpacity style={styles.cancel} onPress={handleClose}>
@@ -48,6 +48,10 @@ const MemberRemove = ({ payload }) => {
48
48
  const { t } = (0, useTranslation_1.default)();
49
49
  return <react_native_1.Text style={styles.text}>{t('chatGroupRemoveMember', { content: payload.names.join('、') })}</react_native_1.Text>;
50
50
  };
51
+ const MemberBeenRemove = ({ payload }) => {
52
+ const { t } = (0, useTranslation_1.default)();
53
+ return <react_native_1.Text style={styles.text}>{t('chatGroupBeenRemoveMember')}</react_native_1.Text>;
54
+ };
51
55
  const MemberLeave = ({ payload }) => {
52
56
  const { t } = (0, useTranslation_1.default)();
53
57
  return <react_native_1.Text style={styles.text}>{t('chatGroupMemberLeave', { userName: payload.userName })}</react_native_1.Text>;
@@ -73,6 +77,9 @@ const PayloadNotify = ({ payload, payloadType }) => {
73
77
  case im_base_1.PayloadType.GROUP_MEMBER_REMOVE_NOTICE:
74
78
  content = <MemberRemove payload={payload}/>;
75
79
  break;
80
+ case im_base_1.PayloadType.GROUP_MEMBER_BEEN_REMOVED:
81
+ content = <MemberBeenRemove payload={payload}/>;
82
+ break;
76
83
  case im_base_1.PayloadType.GROUP_INVITE_VALIDATE_SWITCH:
77
84
  content = <GroupInviteSwitch payload={payload}/>;
78
85
  break;
@@ -123,12 +123,12 @@ const OptionPanel = (0, react_1.forwardRef)((props, ref) => {
123
123
  const fileInfo = files[0];
124
124
  console.log('chooseFile: ', fileInfo);
125
125
  if (!fileInfo.uri) {
126
- rn_ui_1.Toast.error('获取文件路径失败');
126
+ rn_ui_1.Toast.error(t("failedToObtainFilePath"));
127
127
  return;
128
128
  }
129
129
  const ext = (0, file_1.getExtensionFromMime)(fileInfo.type);
130
130
  if (!ext) {
131
- rn_ui_1.Toast.error('不支持的文件类型');
131
+ rn_ui_1.Toast.error(t("unsupportedFileTypes"));
132
132
  return;
133
133
  }
134
134
  if (!fileInfo.name.endsWith(ext)) {
@@ -48,15 +48,54 @@ const im_base_1 = require("@smart-link/im-base");
48
48
  const useTranslation_1 = __importDefault(require("../../../hooks/useTranslation"));
49
49
  const MessagePictureAlbum_1 = __importDefault(require("../../../pages/message/components/MessagePictureAlbum"));
50
50
  const useSearchMessage_1 = require("../../../pages/search/components/useSearchMessage");
51
+ const video_action_1 = require("../../../slice/video/video.action");
51
52
  const numColumns = 4;
52
53
  const { width } = react_native_1.Dimensions.get('window');
53
54
  const SearchPictures = ({ conversationId, multiple }) => {
54
- const { data, onLoadMore, reload } = (0, useSearchMessage_1.useSearchMessage)({
55
+ // 分别获取图片和视频数据
56
+ const { data: pData, onLoadMore: onLoadMorePictures, reload: reloadPictures, } = (0, useSearchMessage_1.useSearchMessage)({
55
57
  keyword: '',
56
58
  conversationId,
57
59
  payloadType: 'picture',
58
60
  normalData: false,
59
61
  });
62
+ const { data: vData, onLoadMore: onLoadMoreVideos, reload: reloadVideos, } = (0, useSearchMessage_1.useSearchMessage)({
63
+ keyword: '',
64
+ conversationId,
65
+ payloadType: 'video',
66
+ normalData: false,
67
+ });
68
+ // 合并数据并按时间排序(最新在前)
69
+ const data = [...pData, ...vData].sort((a, b) => {
70
+ return new Date(b.messageTime).getTime() - new Date(a.messageTime).getTime();
71
+ });
72
+ // 正确合并加载更多功能
73
+ const onLoadMore = () => __awaiter(void 0, void 0, void 0, function* () {
74
+ try {
75
+ yield Promise.all([
76
+ onLoadMorePictures(),
77
+ onLoadMoreVideos()
78
+ ]);
79
+ }
80
+ catch (error) {
81
+ console.error('加载更多失败:', error);
82
+ // 可以添加错误处理逻辑
83
+ }
84
+ });
85
+ // 正确合并刷新功能
86
+ const reload = () => __awaiter(void 0, void 0, void 0, function* () {
87
+ try {
88
+ yield Promise.all([
89
+ reloadPictures(),
90
+ reloadVideos()
91
+ ]);
92
+ }
93
+ catch (error) {
94
+ console.error('刷新失败:', error);
95
+ // 可以添加错误处理逻辑
96
+ }
97
+ });
98
+ // ...其他代码保持不变
60
99
  const [checkedIds, setCheckedIds] = (0, react_1.useState)([]);
61
100
  const navigation = (0, rn_ui_1.useNavigation)();
62
101
  const { t } = (0, useTranslation_1.default)();
@@ -166,15 +205,38 @@ const SearchPictures = ({ conversationId, multiple }) => {
166
205
  });
167
206
  }
168
207
  else {
169
- // 预览图片
170
- setPreview({
171
- visible: true,
172
- messages: [message],
173
- messageSeq: message.messageSeq
174
- });
208
+ // 根据类型处理预览
209
+ if (message.payloadType === im_base_1.PayloadType.PICTURE) {
210
+ // 预览图片
211
+ setPreview({
212
+ visible: true,
213
+ messages: [message],
214
+ messageSeq: message.messageSeq
215
+ });
216
+ }
217
+ else if (message.payloadType === im_base_1.PayloadType.VIDEO) {
218
+ // 播放视频
219
+ (0, init_1.getImManager)().store.dispatch((0, video_action_1.loadVideoPlayer)({
220
+ messageSeq: message.messageSeq,
221
+ payload: message.payload
222
+ }));
223
+ }
175
224
  }
176
225
  }}>
177
- <CachedImage_1.default style={styles.img} localPath={message.payload.localPath} fileId={message.payload.fileId}/>
226
+ {/* 媒体内容容器 */}
227
+ <react_native_1.View style={styles.mediaContainer}>
228
+ {message.payloadType === im_base_1.PayloadType.PICTURE ? (
229
+ // 图片展示
230
+ <CachedImage_1.default style={styles.img} localPath={message.payload.localPath} fileId={message.payload.fileId}/>) : (
231
+ // 视频展示
232
+ <>
233
+ <CachedImage_1.default style={styles.img} localPath={message.payload.localPath} fileId={message.payload.fileId}/>
234
+ {/* 视频播放图标 */}
235
+ <react_native_1.View style={styles.playIcon}>
236
+ <react_native_1.Text style={styles.playIconText}>▶</react_native_1.Text>
237
+ </react_native_1.View>
238
+ </>)}
239
+ </react_native_1.View>
178
240
  </react_native_1.TouchableOpacity>
179
241
  <react_native_1.View style={styles.checkbox}>
180
242
  {multiple && (<rn_ui_1.Checkbox style={{ pointerEvents: 'none' }} checked={checkedIds.includes(message.messageSeq)} size='s'/>)}
@@ -216,5 +278,28 @@ const styles = react_native_1.StyleSheet.create({
216
278
  top: 0,
217
279
  right: 0,
218
280
  },
281
+ mediaContainer: {
282
+ position: 'relative',
283
+ width: '100%',
284
+ height: '100%',
285
+ },
286
+ playIcon: {
287
+ position: 'absolute',
288
+ top: '50%',
289
+ left: '50%',
290
+ transform: [{ translateX: -15 }, { translateY: -15 }],
291
+ width: 30,
292
+ height: 30,
293
+ borderRadius: 15,
294
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
295
+ justifyContent: 'center',
296
+ alignItems: 'center',
297
+ },
298
+ playIconText: {
299
+ color: 'white',
300
+ fontSize: 16,
301
+ fontWeight: 'bold',
302
+ marginLeft: 2, // 让播放图标稍微右移,看起来更居中
303
+ },
219
304
  });
220
305
  exports.default = exports.SearchPictures;
@@ -248,6 +248,10 @@ const extensions = {
248
248
  'application/x-tar': '.tar',
249
249
  'application/x-7z-compressed': '.7z',
250
250
  'application/x-rar-compressed': '.rar',
251
+ // 安卓包
252
+ 'application/octet-stream': '.apk',
253
+ 'application/vnd.android.package-archive': '.apk',
254
+ 'application/x-apk': '.apk',
251
255
  };
252
256
  function getExtensionFromMime(mime) {
253
257
  return extensions[mime] || '';
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@smart-link/rn-im",
3
- "version": "1.0.13",
3
+ "version": "1.0.18",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "dependencies": {
7
7
  "color": "^5.0.0",
8
8
  "lodash-es": "^4.17.21",
9
9
  "emoji-regex": "^10.4.0",
10
- "@smart-link/im-base": "^1.0.13"
10
+ "@smart-link/im-base": "^1.0.16"
11
11
  },
12
12
  "devDependencies": {
13
13
  "@react-native/eslint-config": "0.73.2",