@smart-link/rn-im 1.0.24 → 1.0.26

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 (40) hide show
  1. package/assets/file-dir.jpg +0 -0
  2. package/dist/components/ChatAvatar/ChatAvatarLocal.js +0 -1
  3. package/dist/default-assets.d.ts +1 -0
  4. package/dist/default-assets.js +1 -0
  5. package/dist/interface.d.ts +9 -0
  6. package/dist/pages/conversation/setting/OptionAvatars.d.ts +1 -1
  7. package/dist/pages/conversation/setting/OptionGroup.d.ts +1 -1
  8. package/dist/pages/conversation/setting/Setting.js +2 -3
  9. package/dist/pages/message/FileSelector.d.ts +6 -0
  10. package/dist/pages/message/FileSelector.js +235 -0
  11. package/dist/pages/message/MessageList.js +19 -15
  12. package/dist/pages/message/MessageRecord.js +33 -22
  13. package/dist/pages/message/components/MessageItem.d.ts +2 -1
  14. package/dist/pages/message/components/MessageItem.js +6 -5
  15. package/dist/pages/message/components/MessageOption.d.ts +7 -3
  16. package/dist/pages/message/components/MessageOption.js +13 -16
  17. package/dist/pages/message/components/MessagePayload.d.ts +2 -1
  18. package/dist/pages/message/components/Payload/PayloadFile.d.ts +1 -1
  19. package/dist/pages/message/components/Payload/PayloadFile.js +11 -11
  20. package/dist/pages/message/components/Payload/PayloadMultiple.js +9 -5
  21. package/dist/pages/message/components/Payload/PayloadPicture.d.ts +1 -1
  22. package/dist/pages/message/components/Payload/PayloadPicture.js +3 -4
  23. package/dist/pages/message/components/Payload/PayloadVideo.js +2 -1
  24. package/dist/pages/message/components/Payload/PayloadVoice.js +1 -1
  25. package/dist/pages/message/components/Payload/PayloadWrapper.d.ts +2 -1
  26. package/dist/pages/message/components/Payload/PayloadWrapper.js +8 -3
  27. package/dist/pages/message/components/Payload/type.d.ts +2 -1
  28. package/dist/pages/message/components/ReceiptBack.js +2 -2
  29. package/dist/pages/message/components/TextMixQuote.js +7 -12
  30. package/dist/pages/message/components/TextMixQuoteMessage.js +3 -3
  31. package/dist/pages/message/components/messageBar/OptionPanel.js +6 -31
  32. package/dist/pages/message/message.routes.js +10 -0
  33. package/dist/pages/types.d.ts +4 -2
  34. package/dist/slice/video/video.action.js +9 -1
  35. package/dist/utils/file.d.ts +10 -2
  36. package/dist/utils/file.js +140 -27
  37. package/dist/utils/upload.js +1 -1
  38. package/package.json +2 -2
  39. package/dist/slice/contact/contact.action.d.ts +0 -1
  40. package/dist/slice/contact/contact.action.js +0 -1
Binary file
@@ -81,7 +81,6 @@ const ChatAvatarLocal = ({ fileId, size, name, defaultAvatar }) => {
81
81
  return;
82
82
  }
83
83
  loadAvatar().then(_r => { });
84
- console.log('fileId: ', fileId, name);
85
84
  }, [fileId]);
86
85
  if (ready && avatar) {
87
86
  return (<LocalImage localPath={avatar} style={size ? { width: size, height: size, borderRadius: size / 2 } : { width: 40, height: 40, borderRadius: 20 }}/>);
@@ -17,3 +17,4 @@ export declare const messageBackupImg: {
17
17
  cloud_begin: any;
18
18
  };
19
19
  export declare const noMessageImg: any;
20
+ export declare const dirImg: any;
@@ -18,3 +18,4 @@ export const messageBackupImg = {
18
18
  cloud_begin: require('../assets/cloud-backup-begin.png'),
19
19
  };
20
20
  export const noMessageImg = require('../assets/no_message.png');
21
+ export const dirImg = require('../assets/file-dir.jpg');
@@ -45,3 +45,12 @@ export type IGroup = {
45
45
  parent: boolean;
46
46
  userCount: number;
47
47
  };
48
+ export type IPathItem = {
49
+ path: string;
50
+ isDir: boolean;
51
+ childrenCount: number;
52
+ name: string;
53
+ thumbPath?: string;
54
+ time: number;
55
+ size: number;
56
+ };
@@ -4,7 +4,7 @@ export interface OptionAvatarsProps {
4
4
  list: {
5
5
  userId: string;
6
6
  userName: string;
7
- avatars: string;
7
+ avatars?: string;
8
8
  memberLevel?: MemberLevel;
9
9
  }[];
10
10
  showTitle?: boolean;
@@ -4,7 +4,7 @@ import { ViewProps } from "react-native/Libraries/Components/View/ViewPropTypes"
4
4
  interface IOptionGroupProps extends ViewProps {
5
5
  id: string;
6
6
  name: string;
7
- avatars: string;
7
+ avatars?: string;
8
8
  notice: string;
9
9
  memberLevel: MemberLevel;
10
10
  enableInviteValidate: string;
@@ -110,13 +110,12 @@ const ConversationSetting = ({ navigation }) => {
110
110
  switch (type) {
111
111
  case ConversationType.C2C:
112
112
  content = (<>
113
- <OptionAvatars list={id ? [
113
+ <OptionAvatars list={[
114
114
  {
115
115
  userId: id,
116
116
  userName: name,
117
- avatars,
118
117
  },
119
- ] : []} showAdd={true} showDelete={false} showMore={false} addUser={() => {
118
+ ]} showAdd={true} showDelete={false} showMore={false} addUser={() => {
120
119
  getImManager().store.dispatch(startCreateGroup());
121
120
  navigation.navigate('ChooseContact', {
122
121
  chooseOrg: true,
@@ -0,0 +1,6 @@
1
+ import * as React from 'react';
2
+ import { NativeStackScreenProps } from "@react-navigation/native-stack/lib/typescript/src/types";
3
+ import { IMPageParamList } from "../types";
4
+ type FileSelectorProps = NativeStackScreenProps<IMPageParamList, 'FileSelector'>;
5
+ declare const FileSelector: ({ navigation }: FileSelectorProps) => React.JSX.Element;
6
+ export default FileSelector;
@@ -0,0 +1,235 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ // @flow
11
+ import * as React from 'react';
12
+ import { dp, NavigationPage, SearchInput } from "@smart-link/rn-ui";
13
+ import { View, StyleSheet, TouchableOpacity, ScrollView, Text, FlatList } from "react-native";
14
+ import { Fragment, useCallback, useEffect, useState } from "react";
15
+ import { getFileRootItem, getFileType, isImage, numberToFileSize, readFileDir, selectFile } from "../../utils/file";
16
+ import { getImManager } from "../../init";
17
+ import AntDesign from "react-native-vector-icons/AntDesign";
18
+ import useTranslation from "../../hooks/useTranslation";
19
+ import { findSvgIcon } from "../../utils/file-icon";
20
+ import FastImage from "react-native-fast-image";
21
+ import { dirImg } from "../../default-assets";
22
+ import { formatTime } from "../../hooks/useFormatMsgTime";
23
+ import { useConversation } from "../../hooks/useImSelector";
24
+ const getIcon = (item) => {
25
+ if (item.isDir) {
26
+ return <FastImage style={styles.fileImage} source={dirImg}/>;
27
+ }
28
+ if (isImage(item.path)) {
29
+ return <FastImage style={styles.fileImage} source={{ uri: 'file://' + item.path }}/>;
30
+ }
31
+ if (item.thumbPath) {
32
+ return <FastImage style={styles.fileImage} source={{ uri: 'file://' + item.thumbPath }}/>;
33
+ }
34
+ const Icon = findSvgIcon(getFileType(item.path));
35
+ return <Icon style={styles.fileImage}/>;
36
+ };
37
+ const FileSelector = ({ navigation }) => {
38
+ const scrollHolder = {};
39
+ const [data, setData] = useState([]);
40
+ const [keyword, setKeyword] = useState('');
41
+ const [pathItems, setPathItems] = useState([]);
42
+ const imManager = getImManager();
43
+ const { t, i18n } = useTranslation();
44
+ const { currentConversation } = useConversation();
45
+ useEffect(() => {
46
+ navigation.setOptions({
47
+ headerLeft({ tintColor }) {
48
+ return (<TouchableOpacity activeOpacity={0.5} style={{ padding: dp(10) }} onPress={() => {
49
+ navigation.goBack();
50
+ }}>
51
+ <Text style={{ color: tintColor }}>{t('cancel')}</Text>
52
+ </TouchableOpacity>);
53
+ },
54
+ title: t('file')
55
+ });
56
+ getFileRootItem(imManager).then(items => {
57
+ setData(items);
58
+ });
59
+ }, [navigation, t]);
60
+ const onPressItem = (item) => __awaiter(void 0, void 0, void 0, function* () {
61
+ if (!item.isDir) {
62
+ const localPath = yield selectFile(imManager, item.path, item.name);
63
+ const file = {
64
+ filename: item.name,
65
+ localPath: localPath,
66
+ size: item.size,
67
+ type: getFileType(item.path),
68
+ lastModified: item.time,
69
+ };
70
+ navigation.goBack();
71
+ if (isImage(file.localPath)) {
72
+ imManager.sendPictureMessage(currentConversation, file);
73
+ }
74
+ else {
75
+ imManager.sendFileMessage(currentConversation, file);
76
+ }
77
+ return;
78
+ }
79
+ setPathItems([...pathItems.filter(pathItem => pathItem.path !== item.path), item]);
80
+ scrollHolder.scroll.scrollToEnd();
81
+ readFileDir(imManager, item.path, keyword).then(items => {
82
+ setData(items);
83
+ });
84
+ });
85
+ const onChangeText = useCallback((keyword) => {
86
+ setKeyword(keyword);
87
+ if (pathItems.length === 0) {
88
+ return;
89
+ }
90
+ readFileDir(imManager, pathItems[pathItems.length - 1].path, keyword).then(items => {
91
+ setData(items);
92
+ });
93
+ }, [pathItems]);
94
+ const onPressPathRoot = useCallback(() => {
95
+ setPathItems([]);
96
+ getFileRootItem(imManager).then(items => {
97
+ setData(items);
98
+ });
99
+ }, []);
100
+ const onPressPathItem = useCallback((item) => {
101
+ setPathItems([
102
+ ...pathItems.filter(source => source.path.indexOf(item.path) === -1),
103
+ item
104
+ ]);
105
+ readFileDir(imManager, item.path, keyword).then(items => {
106
+ setData(items);
107
+ });
108
+ }, [pathItems, keyword]);
109
+ const listHeaderComponent = data.length === 0 ? null : <View style={styles.listHeaderLine}/>;
110
+ const listFooterComponent = data.length === 0 ? null : <View style={styles.listFooterLine}/>;
111
+ const itemSeparatorComponent = () => <View style={styles.separator}/>;
112
+ const keyExtractor = (item) => item.name + '_' + item.size;
113
+ const renderItem = ({ item }) => {
114
+ const icon = getIcon(item);
115
+ const extText = item.isDir ? (item.childrenCount + t('items')) : numberToFileSize(item.size);
116
+ return (<TouchableOpacity style={styles.item} onPress={() => {
117
+ onPressItem && onPressItem(item);
118
+ }}>
119
+
120
+ {icon}
121
+ <View style={styles.width15}/>
122
+ <View style={styles.left}>
123
+ <Text style={styles.name} numberOfLines={1} ellipsizeMode={'middle'}>
124
+ {item.name}
125
+ </Text>
126
+ <Text style={styles.time} numberOfLines={1} ellipsizeMode={'middle'}>
127
+ {(item.time ? (formatTime(item.time, t, i18n) + ' - ') : '') + extText}
128
+ </Text>
129
+ </View>
130
+ {item.isDir &&
131
+ <AntDesign name='right' size={dp(14)} color={'#666'} style={styles.accessory}/>}
132
+
133
+ </TouchableOpacity>);
134
+ };
135
+ return (<NavigationPage noPadding>
136
+ <View style={styles.selector}>
137
+ <View style={styles.search}>
138
+ <SearchInput style={{
139
+ height: dp(32),
140
+ backgroundColor: '#ececec',
141
+ }} value={keyword} onChange={onChangeText} onSubmitEditing={() => { }}/>
142
+ </View>
143
+ <View style={styles.scroll}>
144
+ <TouchableOpacity onPress={onPressPathRoot} style={styles.scrollVertical}>
145
+ <Text style={styles.pathText}>{t('position')}</Text>
146
+ </TouchableOpacity>
147
+ <AntDesign name='right' size={dp(16)} color={'#333'} style={styles.scrollVertical}/>
148
+ <ScrollView style={styles.scrollVertical} horizontal={true} ref={v => scrollHolder.scroll = v}>
149
+
150
+ {pathItems.map((pathItem, index) => {
151
+ const disabled = index === pathItems.length - 1;
152
+ return (<Fragment key={index}>
153
+ <TouchableOpacity disabled={disabled} onPress={() => {
154
+ onPressPathItem && onPressPathItem(pathItem);
155
+ }}>
156
+ <Text style={styles.pathText}>{pathItem.name}</Text>
157
+ </TouchableOpacity>
158
+ <AntDesign name='right' size={dp(16)} color={'#333'}/>
159
+ </Fragment>);
160
+ })}
161
+ </ScrollView>
162
+ </View>
163
+ <FlatList contentContainerStyle={styles.list} ItemSeparatorComponent={itemSeparatorComponent} ListHeaderComponent={listHeaderComponent} ListFooterComponent={listFooterComponent} initialNumToRender={20} numColumns={1} data={data} keyboardShouldPersistTaps={'handled'} keyExtractor={keyExtractor} renderItem={renderItem}/>
164
+ </View>
165
+ </NavigationPage>);
166
+ };
167
+ const styles = StyleSheet.create({
168
+ selector: {
169
+ flex: 1,
170
+ backgroundColor: '#fff',
171
+ },
172
+ scroll: {
173
+ flexDirection: 'row',
174
+ paddingHorizontal: dp(8),
175
+ },
176
+ scrollVertical: {
177
+ paddingBottom: dp(10),
178
+ },
179
+ pathText: {
180
+ paddingHorizontal: dp(5),
181
+ fontSize: dp(14),
182
+ lineHeight: dp(16),
183
+ color: '#666',
184
+ },
185
+ search: {
186
+ backgroundColor: '#fff',
187
+ padding: dp(10),
188
+ },
189
+ list: {
190
+ paddingHorizontal: dp(15),
191
+ },
192
+ listFooterLine: {},
193
+ listHeaderLine: {},
194
+ separator: {
195
+ marginLeft: dp(65),
196
+ marginVertical: dp(10),
197
+ height: dp(1),
198
+ backgroundColor: '#ececec'
199
+ },
200
+ width15: {
201
+ width: dp(15),
202
+ },
203
+ width10: {
204
+ width: dp(10),
205
+ },
206
+ item: {
207
+ flexDirection: 'row',
208
+ alignItems: 'center',
209
+ },
210
+ fileImage: {
211
+ borderRadius: dp(5),
212
+ width: dp(49),
213
+ height: dp(49)
214
+ },
215
+ left: {
216
+ justifyContent: 'space-around'
217
+ },
218
+ name: {
219
+ color: '#333',
220
+ fontSize: dp(15),
221
+ lineHeight: dp(20),
222
+ width: dp(260),
223
+ },
224
+ time: {
225
+ color: '#666',
226
+ fontSize: dp(12),
227
+ lineHeight: dp(18),
228
+ },
229
+ accessory: {
230
+ alignSelf: 'center',
231
+ position: 'absolute',
232
+ right: dp(15),
233
+ }
234
+ });
235
+ export default FileSelector;
@@ -27,9 +27,9 @@ const MessageList = ({ navigation }) => {
27
27
  const [showAlbum, setShowAlbum] = useState(false);
28
28
  const [targetMessageSeq, setTargetMessageSeq] = useState('');
29
29
  const [selectMessage, setSelectMessage] = useState();
30
- const [nativeEvent, setNativeEvent] = useState();
31
30
  const [loading, setLoading] = useState(false);
32
- const itemLayout = useRef({});
31
+ const [touchPoint, setTouchPoint] = useState();
32
+ const [targetLayout, setTargetLayout] = useState();
33
33
  const isB2C = (currentConversation === null || currentConversation === void 0 ? void 0 : currentConversation.type) === ConversationType.B2C;
34
34
  useEffect(() => {
35
35
  console.log('[进入会话]: ', currentConversation === null || currentConversation === void 0 ? void 0 : currentConversation.name);
@@ -62,7 +62,7 @@ const MessageList = ({ navigation }) => {
62
62
  InteractionManager.runAfterInteractions(() => {
63
63
  imManager.store.dispatch(MessagePanelActions.saveDraftText(imManager));
64
64
  imManager.leaveConversation();
65
- imManager.store.dispatch(MessageActions.resetState());
65
+ imManager.store.dispatch(MessageActions.resetState({}));
66
66
  });
67
67
  }
68
68
  });
@@ -81,12 +81,22 @@ const MessageList = ({ navigation }) => {
81
81
  getImManager().store.dispatch(MessageActions.setIsSearch(false));
82
82
  }
83
83
  }, [isSearch]);
84
- const onLongPressPopover = useCallback((message, nativeEvent) => {
85
- setNativeEvent(nativeEvent);
84
+ const onPressPopover = useCallback((message, nativeEvent) => {
85
+ if (message.payloadType === PayloadType.PICTURE) {
86
+ setShowAlbum(true);
87
+ setSelectMessage(undefined);
88
+ setTargetMessageSeq(message.messageSeq);
89
+ }
90
+ }, []);
91
+ const onLongPressPopover = useCallback((message, nativeEvent, layout) => {
86
92
  setSelectMessage(message);
93
+ setTouchPoint({
94
+ x: nativeEvent.pageX - nativeEvent.locationX,
95
+ y: nativeEvent.pageY - nativeEvent.locationY
96
+ });
97
+ setTargetLayout(layout);
87
98
  }, []);
88
99
  const onLayoutItem = useMemoizedFn((message, e) => {
89
- itemLayout.current[message.messageSeq] = e.nativeEvent.layout.height;
90
100
  if (rangeList.length > 0 && message.messageSeq === (tips === null || tips === void 0 ? void 0 : tips.messageSeq)) {
91
101
  const imManager = getImManager();
92
102
  imManager.store.dispatch(MessagePanelActions.onRangeLayout(imManager));
@@ -113,13 +123,6 @@ const MessageList = ({ navigation }) => {
113
123
  var _a;
114
124
  (_a = bar.current) === null || _a === void 0 ? void 0 : _a.reset();
115
125
  }, []);
116
- const onPressPopover = useCallback((message, nativeEvent) => {
117
- if (message.payloadType === PayloadType.PICTURE) {
118
- setNativeEvent(nativeEvent);
119
- setShowAlbum(true);
120
- setTargetMessageSeq(message.messageSeq);
121
- }
122
- }, []);
123
126
  const onKeyboardAt = () => {
124
127
  if ((currentConversation === null || currentConversation === void 0 ? void 0 : currentConversation.type) === ConversationType.C2G) {
125
128
  Keyboard.dismiss();
@@ -161,11 +164,12 @@ const MessageList = ({ navigation }) => {
161
164
  {!isB2C && (isMultiple ? <MultipleBar /> : <MessageBar ref={bar} onKeyboardAt={onKeyboardAt}/>)}
162
165
 
163
166
  {tips && tips.type !== TipsType.NONE && (<MessageItemTips {...tips}/>)}
164
- <MessageOption visible={Boolean(selectMessage)} nativeEvent={nativeEvent} message={selectMessage} setLoading={(value) => {
167
+ <MessageOption visible={Boolean(selectMessage)} touchPoint={touchPoint} nativeEventLayout={targetLayout} message={selectMessage} setLoading={(value) => {
165
168
  setLoading(value);
166
169
  }} onClose={() => {
167
- setNativeEvent(undefined);
168
170
  setSelectMessage(undefined);
171
+ setTargetLayout(undefined);
172
+ setTouchPoint(undefined);
169
173
  }}/>
170
174
 
171
175
  <MessagePictureAlbum visible={showAlbum} messages={messages} messageSeq={targetMessageSeq} onClose={() => {
@@ -1,47 +1,63 @@
1
1
  import ChatAvatar from '../../components/ChatAvatar/ChatAvatar';
2
2
  import FormatTimeText from '../../components/FormatTimeText';
3
3
  import { dp, NavigationPage } from '@smart-link/rn-ui';
4
- import React, { useEffect } from 'react';
4
+ import React, { useEffect, useMemo } from 'react';
5
5
  import { View, StyleSheet, FlatList, Text } from 'react-native';
6
6
  import MessagePayload from './components/MessagePayload';
7
+ import { MessageListActions } from '@smart-link/im-base';
7
8
  import MessagePictureAlbum from "./components/MessagePictureAlbum";
8
- import { cloneDeep } from "lodash-es";
9
+ import { useMessage } from "../../hooks/useImSelector";
10
+ import { getImManager } from "../../init";
11
+ import { loadVideoPlayer } from "../../slice/video/video.action";
9
12
  const MessageRecord = ({ navigation, route: { params } }) => {
10
- const { messages, title } = params;
13
+ const { messageSeq, title } = params;
14
+ const { messageListData } = useMessage();
11
15
  useEffect(() => {
12
16
  navigation.setOptions({
13
17
  title,
14
18
  });
19
+ return () => {
20
+ console.log('back');
21
+ getImManager().store.dispatch(MessageListActions.removeMessageListData());
22
+ };
15
23
  }, [navigation, title]);
16
24
  const listRef = React.useRef(null);
17
- const [updateMessages, setUpdateMessages] = React.useState([]);
18
25
  const [previewMessages, setPreviewMessages] = React.useState([]);
19
26
  const [showAlbum, setShowAlbum] = React.useState(false);
20
27
  const [targetMessageSeq, setTargetMessageSeq] = React.useState('');
28
+ const data = useMemo(() => {
29
+ var _a;
30
+ return ((_a = messageListData.find(item => item.messageSeq === messageSeq)) === null || _a === void 0 ? void 0 : _a.childMessages) || [];
31
+ }, [messageSeq, messageListData]);
21
32
  useEffect(() => {
22
33
  var _a;
23
- setUpdateMessages(cloneDeep(messages));
24
34
  (_a = listRef.current) === null || _a === void 0 ? void 0 : _a.scrollToOffset({ animated: false, offset: 0 });
25
- }, [messages]);
35
+ }, [messageSeq]);
26
36
  const onPress = (item) => {
27
- var _a;
28
- // console.log('MessageRecord: ', item);
29
- if (((_a = item.payload.quote) === null || _a === void 0 ? void 0 : _a.quoteMessage.payloadType) === 'picture') {
30
- setShowAlbum(true);
31
- setTargetMessageSeq(item.payload.quote.quoteMessage.messageSeq);
32
- setPreviewMessages([item.payload.quote.quoteMessage]);
33
- return;
37
+ if (item.payload.quote) {
38
+ if (item.payload.quote.quoteMessage.payloadType === 'picture') {
39
+ setShowAlbum(true);
40
+ setTargetMessageSeq(item.payload.quote.quoteMessage.messageSeq);
41
+ setPreviewMessages([item.payload.quote.quoteMessage]);
42
+ return;
43
+ }
44
+ if (item.payload.quote.quoteMessage.payloadType === 'video') {
45
+ getImManager().store.dispatch(loadVideoPlayer({
46
+ messageSeq: item.payload.quote.quoteMessage.messageSeq,
47
+ payload: item.payload.quote.quoteMessage.payload,
48
+ isRecord: true
49
+ }));
50
+ }
34
51
  }
35
52
  if (item.payloadType === 'picture') {
36
53
  setShowAlbum(true);
37
54
  setTargetMessageSeq(item.messageSeq);
38
- setPreviewMessages(updateMessages);
55
+ setPreviewMessages(data);
39
56
  }
40
57
  };
41
58
  return (<NavigationPage noPadding>
42
- <FlatList ref={listRef} contentContainerStyle={styles.list} data={updateMessages} renderItem={({ item, index }) => {
43
- console.log('index: ', index);
44
- const isSamePerson = index > 0 && updateMessages.length > 1 && updateMessages[index - 1].messageFrom === item.messageFrom;
59
+ <FlatList ref={listRef} contentContainerStyle={styles.list} data={data} renderItem={({ item, index }) => {
60
+ const isSamePerson = index > 0 && data.length > 1 && data[index - 1].messageFrom === item.messageFrom;
45
61
  return (<View style={styles.messageItem}>
46
62
  <View style={styles.avatar}>
47
63
  {!isSamePerson && (<ChatAvatar id={item.messageFrom} url={item.payload.avatars} size={dp(40)} name={item.messageFromName}/>)}
@@ -53,11 +69,6 @@ const MessageRecord = ({ navigation, route: { params } }) => {
53
69
  </View>
54
70
  <MessagePayload {...item} isRecord direction="left" showArrow={false} onPress={() => {
55
71
  onPress(item);
56
- }} onLoad={(localPath) => {
57
- console.log('localPath: ', localPath);
58
- if (!item.payload.localPath) {
59
- item.payload.localPath = localPath;
60
- }
61
72
  }}/>
62
73
  </View>
63
74
  </View>);
@@ -1,6 +1,7 @@
1
1
  import { IConversation, IMessage } from '@smart-link/im-base';
2
2
  import React from 'react';
3
3
  import { NativeTouchEvent, LayoutChangeEvent } from 'react-native';
4
+ import { NativeEventLayout } from "../../types";
4
5
  type MessageItemProps = {
5
6
  isMultiple?: boolean;
6
7
  multipleSelect?: Record<string, IMessage>;
@@ -8,7 +9,7 @@ type MessageItemProps = {
8
9
  message: IMessage;
9
10
  mine: boolean;
10
11
  showTimeLine: boolean;
11
- onLongPressPopover: (message: IMessage, nativeEvent: NativeTouchEvent) => void;
12
+ onLongPressPopover: (message: IMessage, nativeEvent: NativeTouchEvent, layout: NativeEventLayout) => void;
12
13
  onPressPopover: (message: IMessage, nativeEvent: NativeTouchEvent) => void;
13
14
  onLayoutItem: (item: IMessage, e: LayoutChangeEvent) => void;
14
15
  };
@@ -33,7 +33,7 @@ const MessageItem = ({ isMultiple, multipleSelect, conversation, message, mine,
33
33
  {isNotify && <PayloadNotify {...message}/>}
34
34
  {isUndo && <MessageUndo messageSeq={message.messageSeq} text={mine ? t('youWithdrawMsg') : `"${message.messageFromName}" ${t('withdrawMsg')}`} undoText={message.payload.text} haveBackgroundImage={false} edit={mine && message.payloadType === 'text'}/>}
35
35
  {isMessagePayload && (<>
36
- {isMultiple && (<TouchableWithoutFeedback onPress={() => {
36
+ {isMultiple && message.payloadType !== 'voice' && (<TouchableWithoutFeedback onPress={() => {
37
37
  getImManager().store.dispatch(MessagePanelActions.onMultipleSelect(message.messageSeq));
38
38
  }}>
39
39
  <View style={styles.multipleWrap}/>
@@ -45,7 +45,7 @@ const MessageItem = ({ isMultiple, multipleSelect, conversation, message, mine,
45
45
  justifyContent: 'flex-start',
46
46
  },
47
47
  ]}>
48
- {mine && isMultiple && (<Checkbox checked={isMultipleChecked} style={[styles.multipleCheckbox]}/>)}
48
+ {mine && isMultiple && message.payloadType !== 'voice' && (<Checkbox checked={isMultipleChecked} style={[styles.multipleCheckbox]}/>)}
49
49
  <ChatAvatar name={message.messageFromName} id={message.messageFrom} size={dp(40)}/>
50
50
  <View style={[
51
51
  {
@@ -58,6 +58,7 @@ const MessageItem = ({ isMultiple, multipleSelect, conversation, message, mine,
58
58
  ]}>
59
59
  {!mine && message.conversationType !== ConversationType.C2C && (<Text style={[styles.name, {
60
60
  marginLeft: notArrow ? 0 : dp(10),
61
+ marginBottom: dp(5),
61
62
  }]}>{message.messageFromName}</Text>)}
62
63
  <View>
63
64
  <MessagePayload style={[
@@ -70,10 +71,10 @@ const MessageItem = ({ isMultiple, multipleSelect, conversation, message, mine,
70
71
  e.stopPropagation();
71
72
  e.preventDefault();
72
73
  onPressPopover(message, e.nativeEvent);
73
- }} onLongPress={e => {
74
+ }} onLongPress={(e, layout) => {
74
75
  e.stopPropagation();
75
76
  e.preventDefault();
76
- onLongPressPopover(message, e.nativeEvent);
77
+ onLongPressPopover(message, e.nativeEvent, layout);
77
78
  }}/>
78
79
  {isC2C && mine && <MessageReceiptStatus {...message}/>}
79
80
  </View>
@@ -82,7 +83,7 @@ const MessageItem = ({ isMultiple, multipleSelect, conversation, message, mine,
82
83
  <Icon name="info" size={dp(20)} color={theme.errorColor}/>
83
84
  </Text>)}
84
85
  {mine && message.messageStatus === MessageStatus.EMITTING && (<ActivityIndicator size="small" color={'#666'} animating={true}/>)}
85
- {!mine && isMultiple && (<Checkbox checked={isMultipleChecked} style={[styles.multipleCheckbox, styles.multipleCheckboxRight]}/>)}
86
+ {!mine && isMultiple && message.payloadType !== 'voice' && (<Checkbox checked={isMultipleChecked} style={[styles.multipleCheckbox, styles.multipleCheckboxRight]}/>)}
86
87
  </View>
87
88
  </>)}
88
89
  {message.messageStatus === MessageStatus.EMIT_ERROR && (<>
@@ -1,12 +1,16 @@
1
1
  import React from 'react';
2
- import { NativeTouchEvent } from 'react-native';
3
2
  import { IMessage } from '@smart-link/im-base';
3
+ import { NativeEventLayout } from '../../../pages';
4
4
  type MessageOptionProps = {
5
5
  message?: IMessage;
6
6
  visible: boolean;
7
- nativeEvent?: NativeTouchEvent;
7
+ touchPoint?: {
8
+ x: number;
9
+ y: number;
10
+ };
11
+ nativeEventLayout?: NativeEventLayout;
8
12
  onClose: () => void;
9
13
  setLoading: (loading: boolean) => void;
10
14
  };
11
- declare const _default: React.MemoExoticComponent<({ visible, message, nativeEvent, onClose, setLoading }: MessageOptionProps) => React.JSX.Element | null>;
15
+ declare const _default: React.MemoExoticComponent<({ visible, message, nativeEventLayout, touchPoint, onClose, setLoading }: MessageOptionProps) => React.JSX.Element | null>;
12
16
  export default _default;
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import React, { memo, useRef } from 'react';
11
- import { Animated, Text, StyleSheet, TouchableOpacity, View, TouchableWithoutFeedback, Dimensions, UIManager, StatusBar, Modal, } from 'react-native';
11
+ import { Animated, Text, StyleSheet, TouchableOpacity, View, TouchableWithoutFeedback, Dimensions, StatusBar, Modal, } from 'react-native';
12
12
  import Clipboard from '@react-native-clipboard/clipboard';
13
13
  import { getMessageActionKeys, MessageActions, MessagePanelActions, MessageStatus, PayloadType, ConversationType, } from '@smart-link/im-base';
14
14
  import AntDesign from 'react-native-vector-icons/AntDesign';
@@ -198,7 +198,7 @@ const renderOperations = (items, close) => {
198
198
  })}
199
199
  </View>);
200
200
  };
201
- const MessageOption = ({ visible, message, nativeEvent, onClose, setLoading }) => {
201
+ const MessageOption = ({ visible, message, nativeEventLayout, touchPoint, onClose, setLoading }) => {
202
202
  var _a;
203
203
  const { t } = useTranslation();
204
204
  const headerHeight = useHeaderHeight();
@@ -258,20 +258,17 @@ const MessageOption = ({ visible, message, nativeEvent, onClose, setLoading }) =
258
258
  const { width: contentWidth, height: contentHeight } = e.nativeEvent.layout;
259
259
  // 计算操作项的显示位置
260
260
  // 判断点击位置是否存在
261
- if (nativeEvent) {
262
- // 测量点击位置的元素
263
- // @ts-ignore
264
- UIManager.measure(nativeEvent.target, (x, y, width, height, pageX, pageY) => {
265
- console.log('measure', x, y, width, height, pageX, pageY);
266
- let targetY = 0;
267
- if (pageY + height + contentHeight > displayArea.height) {
268
- targetY = pageY - contentHeight;
269
- }
270
- else {
271
- targetY = pageY + height;
272
- }
273
- showAnimation(displayArea.x + displayArea.width - contentWidth, targetY);
274
- });
261
+ if (nativeEventLayout && touchPoint) {
262
+ const { height } = nativeEventLayout;
263
+ const { x, y } = touchPoint;
264
+ let safePageY = !isNaN(y) ? y : displayArea.y;
265
+ let targetY = safePageY + height + contentHeight > displayArea.height
266
+ ? safePageY - contentHeight
267
+ : safePageY + height;
268
+ let targetX = x + contentWidth > displayArea.width
269
+ ? displayArea.width - contentWidth
270
+ : x;
271
+ showAnimation(targetX, targetY);
275
272
  }
276
273
  else {
277
274
  showAnimation(displayArea.x + displayArea.width - contentWidth, displayArea.y + displayArea.height / 2 - contentHeight / 2);
@@ -3,13 +3,14 @@ import { IMessage } from '@smart-link/im-base';
3
3
  import { StyleProp } from 'react-native';
4
4
  import { ViewStyle } from 'react-native/Libraries/StyleSheet/StyleSheetTypes';
5
5
  import { GestureResponderEvent } from 'react-native/Libraries/Types/CoreEventTypes';
6
+ import { NativeEventLayout } from "../../types";
6
7
  export interface MessagePayloadProps extends IMessage {
7
8
  style?: StyleProp<ViewStyle>;
8
9
  direction?: 'left' | 'right';
9
10
  showArrow?: boolean;
10
11
  isRecord?: boolean;
11
12
  onPress?: (e: GestureResponderEvent) => void;
12
- onLongPress?: (e: GestureResponderEvent) => void;
13
+ onLongPress?: (e: GestureResponderEvent, layout: NativeEventLayout) => void;
13
14
  onLoad?: (localPath: string) => void;
14
15
  }
15
16
  declare const _default: React.NamedExoticComponent<MessagePayloadProps>;
@@ -1,4 +1,4 @@
1
1
  import React from 'react';
2
2
  import { PayloadProps } from './type';
3
- declare const _default: React.MemoExoticComponent<({ payload, messageSeq, direction, showArrow, isRecord, messageStatus, sendSize, style, onLongPress }: PayloadProps) => React.JSX.Element>;
3
+ declare const _default: React.MemoExoticComponent<({ payload, messageSeq, direction, showArrow, isRecord, messageStatus, jobId, sendSize, style, onLongPress }: PayloadProps) => React.JSX.Element>;
4
4
  export default _default;