ChattingScreen.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. import React, {Component} from 'react';
  2. import CommonTitleBar from '../views/CommonTitleBar';
  3. import Global from '../utils/Global';
  4. import Utils from '../utils/Utils';
  5. import TimeUtils from '../utils/TimeUtil';
  6. import TimeUtil from '../utils/TimeUtil';
  7. import ChatBottomBar from '../views/ChatBottomBar';
  8. import EmojiView from '../views/EmojiView';
  9. import MoreView from '../views/MoreView';
  10. import LoadingView from '../views/LoadingView';
  11. import StorageUtil from '../utils/StorageUtil';
  12. import CountEmitter from '../event/CountEmitter';
  13. import ConversationUtil from '../utils/ConversationUtil';
  14. import WebIM from '../Lib/WebIM';
  15. import {Dimensions, FlatList, Image, PixelRatio, StyleSheet, Text, View, ToastAndroid} from 'react-native';
  16. const {width} = Dimensions.get('window');
  17. const MSG_LINE_MAX_COUNT = 15;
  18. export default class ChattingScreen extends Component {
  19. constructor(props) {
  20. super(props);
  21. this.state = {
  22. showEmojiView: false,
  23. showMoreView: false,
  24. showProgress: false,
  25. isSessionStarted: false,
  26. conversation: null,
  27. messages: []
  28. };
  29. this.chatContactId = this.props.navigation.state.params.contactId;
  30. this.chatUsername = this.props.navigation.state.params.name;
  31. this.chatWithAvatar = this.props.navigation.state.params.avatar;
  32. StorageUtil.get('username', (error, object) => {
  33. if (!error && object != null) {
  34. let username = object.username;
  35. StorageUtil.get('userInfo-' + username, (error, object) => {
  36. //获取当前用户信息
  37. if (!error && object) {
  38. this.setState({userInfo: object.info});
  39. }
  40. });
  41. this.username = username;
  42. this.conversationId = ConversationUtil.generateConversationId(this.chatContactId, username);
  43. ConversationUtil.getConversation(this.conversationId, (data) => {
  44. if (data != null) {
  45. this.setState({conversation: data, messages: data.messages});
  46. }
  47. })
  48. }
  49. });
  50. }
  51. componentWillMount() {
  52. CountEmitter.addListener('notifyChattingRefresh', () => {
  53. // 刷新消息
  54. let conversationId = ConversationUtil.generateConversationId(this.chatContactId, this.username);
  55. ConversationUtil.getConversation(conversationId, (data) => {
  56. if (data != null) {
  57. this.setState({conversation: data, messages: data.messages}, ()=>{
  58. this.scroll();
  59. });
  60. }
  61. });
  62. });
  63. }
  64. render() {
  65. var moreView = [];
  66. if (this.state.showEmojiView) {
  67. moreView.push(
  68. <View key={"emoji-view-key"}>
  69. <View style={{width: width, height: 1 / PixelRatio.get(), backgroundColor: Global.dividerColor}}/>
  70. <View style={{height: Global.emojiViewHeight}}>
  71. <EmojiView/>
  72. </View>
  73. </View>
  74. );
  75. }
  76. if (this.state.showMoreView) {
  77. moreView.push(
  78. <View key={"more-view-key"}>
  79. <View style={{width: width, height: 1 / PixelRatio.get(), backgroundColor: Global.dividerColor}}/>
  80. <View style={{height: Global.emojiViewHeight}}>
  81. <MoreView
  82. sendImageMessage={this.sendImageMessage.bind(this)}
  83. />
  84. </View>
  85. </View>
  86. );
  87. }
  88. return (
  89. <View style={styles.container}>
  90. <CommonTitleBar title={this.chatUsername} nav={this.props.navigation}/>
  91. {
  92. this.state.showProgress ? (
  93. <LoadingView cancel={() => this.setState({showProgress: false})}/>
  94. ) : (null)
  95. }
  96. <View style={styles.content}>
  97. <FlatList
  98. ref="flatList"
  99. data={this.state.messages}
  100. renderItem={this.renderItem}
  101. keyExtractor={this._keyExtractor}
  102. extraData={this.state}
  103. />
  104. </View>
  105. <View style={styles.divider}/>
  106. <View style={styles.bottomBar}>
  107. <ChatBottomBar updateView={this.updateView} handleSendBtnClick={this.handleSendBtnClick}/>
  108. </View>
  109. {moreView}
  110. </View>
  111. );
  112. }
  113. handleSendBtnClick = (msg) => {
  114. this.sendTextMessage(msg);
  115. }
  116. sendTextMessage(message) { // 发送文本消息
  117. let id = WebIM.conn.getUniqueId(); // 生成本地消息id
  118. let msg = new WebIM.message('txt', id); // 创建文本消息
  119. msg.set({
  120. msg: message, // 消息内容
  121. to: this.chatContactId, // 接收消息对象(用户id)
  122. roomType: false,
  123. success: function (id, serverMsgId) {
  124. },
  125. fail: function (e) {
  126. }
  127. });
  128. msg.body.chatType = 'singleChat';
  129. if (this.chatContactId != 'tulingrobot') {
  130. // 不是跟图灵机器人聊天,则调用环信的发送消息接口
  131. WebIM.conn.send(msg.body);
  132. } else {
  133. // 跟图灵机器人聊天
  134. this.chatWithTuling(message);
  135. }
  136. // 还需要将本条消息添加到当前会话中
  137. this.concatMessage({
  138. 'conversationId': ConversationUtil.generateConversationId(this.chatContactId, this.username),
  139. 'id': id,
  140. 'from': this.username,
  141. 'to': this.chatContactId,
  142. 'time': TimeUtil.currentTime(),
  143. 'data': message,
  144. 'msgType': 'txt'
  145. })
  146. }
  147. sendImageMessage(image) { // 发送图片消息
  148. let imagePath = image.path;
  149. let imageName = null;
  150. if (!Utils.isEmpty(imagePath)) {
  151. imageName = imagePath.substring(imagePath.lastIndexOf('/') + 1, imagePath.length);
  152. }
  153. let imageSize = image.size;
  154. let imageType = imageName && imageName.split('.').pop();
  155. let imageWidth = image.width;
  156. let imageHeight = image.height;
  157. let id = WebIM.conn.getUniqueId(); // 生成本地消息id
  158. let msg = new WebIM.message('img', id); // 创建文本消息
  159. let to = this.chatContactId;
  160. msg.set({
  161. apiUrl: WebIM.config.apiURL,
  162. ext: {
  163. filelength: imageSize,
  164. filename: imageName,
  165. filetype: imageType,
  166. width: imageWidth,
  167. height: imageHeight
  168. },
  169. file: {
  170. data: {
  171. uri: imagePath, type: 'application/octet-stream', name: imageName
  172. }
  173. },
  174. to,
  175. roomType: false,
  176. onFileUploadError: function (error) {
  177. console.log('onFileUploadError: ' + JSON.stringify(error))
  178. },
  179. onFileUploadComplete: function (data) {
  180. console.log('onFileUploadComplete')
  181. },
  182. success: function (id) {
  183. console.log('success')
  184. }
  185. });
  186. // WebIM.conn.send(msg.body);
  187. if (this.chatContactId != 'tulingrobot') {
  188. // 不是跟图灵机器人聊天,则调用环信的发送消息接口
  189. WebIM.conn.send(msg.body);
  190. } else {
  191. // 跟图灵机器人聊天
  192. this.chatWithTuling('[图片]');
  193. }
  194. this.concatMessage({
  195. 'conversationId': ConversationUtil.generateConversationId(this.chatContactId, this.username),
  196. 'id': id,
  197. 'from': this.username,
  198. 'to': this.chatContactId,
  199. 'time': TimeUtil.currentTime(),
  200. 'url': imagePath,
  201. 'ext': {width: imageWidth, height: imageHeight},
  202. 'msgType': 'img'
  203. })
  204. }
  205. chatWithTuling(message) {
  206. let url = "https://app.yubo725.top/autoreply";
  207. let formData = new FormData();
  208. formData.append('key', message);
  209. fetch(url, {method: 'POST', body: formData})
  210. .then((res)=>res.json())
  211. .then((json)=>{
  212. if (!Utils.isEmpty(json)) {
  213. if (json.code == 1) {
  214. // 机器人的回复
  215. let reply = json.msg;
  216. this.addAutoRelpyMsg(reply);
  217. } else {
  218. ToastAndroid.show(json.msg, ToastAndroid.SHORT);
  219. }
  220. }
  221. })
  222. }
  223. addAutoRelpyMsg(message) {
  224. let id = WebIM.conn.getUniqueId(); // 生成本地消息id
  225. let msg = new WebIM.message('txt', id); // 创建文本消息
  226. msg.set({
  227. msg: message, // 消息内容
  228. to: this.username, // 接收消息对象(用户id)
  229. roomType: false,
  230. success: function (id, serverMsgId) {
  231. },
  232. fail: function (e) {
  233. }
  234. });
  235. msg.body.chatType = 'singleChat';
  236. ConversationUtil.addMessage({
  237. 'conversationId': ConversationUtil.generateConversationId(this.chatContactId, this.username),
  238. 'id': id,
  239. 'from': this.chatContactId,
  240. 'to': this.username,
  241. 'time': TimeUtil.currentTime(),
  242. 'data': message,
  243. 'msgType': 'txt'
  244. }, ()=>{
  245. CountEmitter.emit('notifyChattingRefresh');
  246. });
  247. }
  248. scroll() {
  249. this.scrollTimeout = setTimeout(() => this.refs.flatList.scrollToEnd(), 0);
  250. }
  251. concatMessage(message) {
  252. ConversationUtil.addMessage(message, () => {
  253. // 发送完消息,还要通知会话列表更新
  254. CountEmitter.emit('notifyConversationListRefresh');
  255. });
  256. let msgs = this.state.messages;
  257. msgs.push(message);
  258. console.log(msgs);
  259. this.setState({messages: msgs}, ()=>{
  260. this.scroll();
  261. });
  262. }
  263. componentWillUnmount() {
  264. this.scrollTimeout && clearTimeout(this.scrollTimeout);
  265. CountEmitter.removeListener('notifyChattingRefresh', ()=>{});
  266. // 通知会话列表刷新未读数
  267. if (this.conversationId) {
  268. ConversationUtil.clearUnreadCount(this.conversationId, ()=>{
  269. CountEmitter.emit('notifyConversationListRefresh');
  270. })
  271. }
  272. }
  273. updateView = (emoji, more) => {
  274. this.setState({
  275. showEmojiView: emoji,
  276. showMoreView: more,
  277. })
  278. }
  279. // 当str长度超过某个限定值时,在str中插入换行符
  280. spliceStr(str) {
  281. var len = str.length;
  282. if (len > MSG_LINE_MAX_COUNT) {
  283. var pageSize = parseInt(len / MSG_LINE_MAX_COUNT) + 1;
  284. var result = '';
  285. var start, end;
  286. for (var i = 0; i < pageSize; i++) {
  287. start = i * MSG_LINE_MAX_COUNT;
  288. end = start + MSG_LINE_MAX_COUNT;
  289. if (end > len) {
  290. end = len;
  291. }
  292. result += str.substring(start, end);
  293. result += '\n';
  294. }
  295. return result;
  296. } else {
  297. return str;
  298. }
  299. }
  300. _keyExtractor = (item, index) => index
  301. shouldShowTime(item) { // 该方法判断当前消息是否需要显示时间
  302. let index = item.index;
  303. if (index == 0) {
  304. // 第一条消息,显示时间
  305. return true;
  306. }
  307. if (index > 0) {
  308. let messages = this.state.messages;
  309. if (!Utils.isEmpty(messages) && messages.length > 0) {
  310. let preMsg = messages[index - 1];
  311. let delta = item.item.time - preMsg.time;
  312. if (delta > 3 * 60) {
  313. return true;
  314. }
  315. }
  316. return false;
  317. }
  318. }
  319. renderItem = (item) => {
  320. let msgType = item.item.msgType;
  321. if (msgType == 'txt') {
  322. // 文本消息
  323. if (item.item.to == this.username) {
  324. return this.renderReceivedTextMsg(item);
  325. } else {
  326. return this.renderSendTextMsg(item);
  327. }
  328. } else if (msgType == 'img') {
  329. // 图片消息
  330. if (item.item.to == this.username) {
  331. return this.renderReceivedImgMsg(item);
  332. } else {
  333. return this.renderSendImgMsg(item);
  334. }
  335. }
  336. }
  337. renderReceivedTextMsg(item) { // 接收的文本消息
  338. let contactAvatar = require('../../images/avatar.png');
  339. if (!Utils.isEmpty(this.chatWithAvatar)) {
  340. contactAvatar = this.chatWithAvatar;
  341. }
  342. return (
  343. <View style={{flexDirection: 'column', alignItems: 'center'}}>
  344. {
  345. this.shouldShowTime(item) ? (
  346. <Text style={listItemStyle.time}>{TimeUtils.formatChatTime(parseInt(item.item.time))}</Text>
  347. ) : (null)
  348. }
  349. <View style={listItemStyle.container}>
  350. <Image style={listItemStyle.avatar} source={contactAvatar}/>
  351. <View style={listItemStyle.msgContainer}>
  352. <Text style={listItemStyle.msgText}>{this.spliceStr(item.item.data)}</Text>
  353. </View>
  354. </View>
  355. </View>
  356. );
  357. }
  358. renderSendTextMsg(item) { // 发送出去的文本消息
  359. let avatar = require('../../images/avatar.png');
  360. if (!Utils.isEmpty(this.state.userInfo.avatar)) {
  361. avatar = {uri: this.state.userInfo.avatar};
  362. }
  363. // 发送出去的消息
  364. return (
  365. <View style={{flexDirection: 'column', alignItems: 'center'}}>
  366. {
  367. this.shouldShowTime(item) ? (
  368. <Text style={listItemStyle.time}>{TimeUtils.formatChatTime(parseInt(item.item.time))}</Text>
  369. ) : (null)
  370. }
  371. <View style={listItemStyle.containerSend}>
  372. <View style={listItemStyle.msgContainerSend}>
  373. <Text style={listItemStyle.msgText}>{this.spliceStr(item.item.data)}</Text>
  374. </View>
  375. <Image style={listItemStyle.avatar} source={avatar}/>
  376. </View>
  377. </View>
  378. );
  379. }
  380. renderReceivedImgMsg(item) { // 接收的图片消息
  381. let contactAvatar = require('../../images/avatar.png');
  382. if (!Utils.isEmpty(this.chatWithAvatar)) {
  383. contactAvatar = this.chatWithAvatar;
  384. }
  385. return (
  386. <View style={{flexDirection: 'column', alignItems: 'center'}}>
  387. {
  388. this.shouldShowTime(item) ? (
  389. <Text style={listItemStyle.time}>{TimeUtils.formatChatTime(parseInt(item.item.time))}</Text>
  390. ) : (null)
  391. }
  392. <View style={listItemStyle.container}>
  393. <Image style={listItemStyle.avatar} source={contactAvatar}/>
  394. <View style={[listItemStyle.msgContainer, {paddingLeft: 0, paddingRight: 0}]}>
  395. <Image
  396. source={{uri: item.item.url}}
  397. style={{width: 150, height: 150 * (item.item.ext.height / item.item.ext.width)}}
  398. />
  399. </View>
  400. </View>
  401. </View>
  402. );
  403. }
  404. renderSendImgMsg(item) { // 发送的图片消息
  405. let avatar = require('../../images/avatar.png');
  406. if (!Utils.isEmpty(this.state.userInfo.avatar)) {
  407. avatar = {uri: this.state.userInfo.avatar};
  408. }
  409. // 发送出去的消息
  410. return (
  411. <View style={{flexDirection: 'column', alignItems: 'center'}}>
  412. {
  413. this.shouldShowTime(item) ? (
  414. <Text style={listItemStyle.time}>{TimeUtils.formatChatTime(parseInt(item.item.time))}</Text>
  415. ) : (null)
  416. }
  417. <View style={listItemStyle.containerSend}>
  418. <View style={[listItemStyle.msgContainerSend, {paddingLeft: 0, paddingRight: 0}]}>
  419. <Image
  420. source={{uri: item.item.url}}
  421. style={{borderRadius: 3, width: 150, height: 150 * (item.item.ext.height / item.item.ext.width)}}
  422. />
  423. </View>
  424. <Image style={listItemStyle.avatar} source={avatar}/>
  425. </View>
  426. </View>
  427. );
  428. }
  429. }
  430. const listItemStyle = StyleSheet.create({
  431. container: {
  432. flex: 1,
  433. width: width,
  434. flexDirection: 'row',
  435. padding: 5,
  436. },
  437. avatar: {
  438. width: 40,
  439. height: 40,
  440. },
  441. msgContainer: {
  442. backgroundColor: '#FFFFFF',
  443. borderRadius: 3,
  444. paddingLeft: 8,
  445. paddingRight: 8,
  446. justifyContent: 'center',
  447. alignItems: 'center',
  448. marginLeft: 5,
  449. },
  450. msgContainerSend: {
  451. backgroundColor: '#9FE658',
  452. borderRadius: 3,
  453. paddingLeft: 8,
  454. paddingRight: 8,
  455. justifyContent: 'center',
  456. alignItems: 'center',
  457. marginRight: 5,
  458. },
  459. msgText: {
  460. fontSize: 15,
  461. lineHeight: 24,
  462. },
  463. containerSend: {
  464. flex: 1,
  465. width: width,
  466. flexDirection: 'row',
  467. padding: 5,
  468. justifyContent: 'flex-end',
  469. },
  470. time: {
  471. backgroundColor: '#D4D4D4',
  472. paddingLeft: 6,
  473. paddingRight: 6,
  474. paddingTop: 4,
  475. paddingBottom: 4,
  476. borderRadius: 5,
  477. color: '#FFFFFF',
  478. marginTop: 10,
  479. fontSize: 11,
  480. }
  481. });
  482. const styles = StyleSheet.create({
  483. container: {
  484. flex: 1,
  485. flexDirection: 'column',
  486. },
  487. content: {
  488. flex: 1,
  489. flexDirection: 'column',
  490. alignItems: 'flex-start',
  491. backgroundColor: Global.pageBackgroundColor
  492. },
  493. bottomBar: {
  494. height: 50,
  495. },
  496. divider: {
  497. width: width,
  498. height: 1 / PixelRatio.get(),
  499. backgroundColor: Global.dividerColor,
  500. }
  501. });