No Description

ChatInputBar.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. /**
  2. * Desc: 聊天输入框
  3. *
  4. * Created by WangGanxin on 2018/1/29
  5. * Email: mail@wangganxin.me
  6. */
  7. import React, {Component,PureComponent} from 'react';
  8. import {
  9. StyleSheet,
  10. Text,
  11. View,
  12. Image,
  13. TextInput,
  14. TouchableWithoutFeedback,
  15. Platform,
  16. KeyboardAvoidingView,
  17. Keyboard,
  18. Modal,
  19. } from 'react-native';
  20. import PropTypes from 'prop-types';
  21. import EmotionsView from './EmotionsView';
  22. import ModalBox from 'react-native-modalbox';
  23. import Line from './Line';
  24. import {EMOTIONS_ZHCN,invertKeyValues} from './DataSource';
  25. let emojiReg = new RegExp('\\[[^\\]]+\\]','g'); //表情符号正则表达式
  26. export default class ChatInputBar extends PureComponent {
  27. constructor(props){
  28. super(props);
  29. this.state = {
  30. isEmotionsVisible:false, //表情框是否可见
  31. modalVisible:false,
  32. inputValue:'',
  33. cursorIndex:0,
  34. autoFocus:true,
  35. tempSendTxtArray:[],
  36. };
  37. }
  38. openInputBar(){
  39. if (Platform.OS === 'android'){
  40. this.refs.modal.open();
  41. }
  42. else {
  43. this.setState({
  44. modalVisible:!this.state.modalVisible,
  45. });
  46. }
  47. }
  48. closeInputBar(){
  49. if (Platform.OS === 'android'){
  50. this.refs.modal.close();
  51. }
  52. else {
  53. this.setState({
  54. modalVisible:false,
  55. });
  56. }
  57. }
  58. _matchContentString(textContent){
  59. // 匹配得到index并放入数组中
  60. let currentTextLength = textContent.length;
  61. let emojiIndex = textContent.search(emojiReg);
  62. let checkIndexArray = [];
  63. // 若匹配不到,则直接返回一个全文本
  64. if (emojiIndex === -1) {
  65. this.state.tempSendTxtArray.push(textContent.substring(0,currentTextLength));
  66. } else {
  67. if (emojiIndex !== -1) {
  68. checkIndexArray.push(emojiIndex);
  69. }
  70. // 取index最小者
  71. let minIndex = Math.min(...checkIndexArray);
  72. // 将0-index部分返回文本
  73. this.state.tempSendTxtArray.push(textContent.substring(0, minIndex));
  74. // 将index部分作分别处理
  75. this._matchEmojiString(textContent.substring(minIndex));
  76. }
  77. }
  78. _matchEmojiString(emojiStr) {
  79. let castStr = emojiStr.match(emojiReg);
  80. let emojiLength = castStr[0].length;
  81. let emotoins_code = invertKeyValues(EMOTIONS_ZHCN);
  82. this.state.tempSendTxtArray.push(emotoins_code[castStr]);
  83. this._matchContentString(emojiStr.substring(emojiLength));
  84. }
  85. _toogleShowEmojiView(){
  86. if (!this.state.isEmotionsVisible){
  87. Keyboard.dismiss();
  88. }
  89. this.setState({
  90. isEmotionsVisible:!this.state.isEmotionsVisible,
  91. });
  92. }
  93. _onModalBoxClosed(){
  94. this.setState({
  95. isEmotionsVisible:false,
  96. });
  97. }
  98. _onEmojiSelected(code){
  99. if (code === '' ){
  100. return;
  101. }
  102. let lastText = '';
  103. let currentTextLength = this.state.inputValue.length;
  104. if (code === '/{del'){ //删除键
  105. if (currentTextLength === 0){
  106. return;
  107. }
  108. if (this.state.cursorIndex < currentTextLength){ //光标在字符串中间
  109. let emojiReg = new RegExp('\\[[^\\]]+\\]'); //表情符号正则表达式
  110. let emojiIndex = this.state.inputValue.search(emojiReg); //匹配到的第一个表情符位置
  111. if (emojiIndex === -1){ //没有匹配到表情符
  112. let preStr = this.state.inputValue.substring(0,this.state.cursorIndex);
  113. let nextStr = this.state.inputValue.substring(this.state.cursorIndex);
  114. lastText = preStr.substring(0,preStr.length - 1) + nextStr;
  115. this.setState({
  116. cursorIndex:preStr.length - 1,
  117. });
  118. }
  119. else {
  120. let preStr = this.state.inputValue.substring(0,this.state.cursorIndex);
  121. let nextStr = this.state.inputValue.substring(this.state.cursorIndex);
  122. let lastChar = preStr.charAt(preStr.length - 1);
  123. if (lastChar === ']'){
  124. let castArray = preStr.match(emojiReg);
  125. if(!castArray){
  126. let cast = castArray[castArray.length - 1];
  127. lastText = preStr.substring(0,preStr.length - cast.length) + nextStr;
  128. this.setState({
  129. cursorIndex:preStr.length - cast.length,
  130. });
  131. }
  132. else{
  133. lastText = preStr.substring(0,preStr.length - 1) + nextStr;
  134. this.setState({
  135. cursorIndex:preStr.length - 1,
  136. });
  137. }
  138. } else {
  139. lastText = preStr.substring(0,preStr.length - 1) + nextStr;
  140. this.setState({
  141. cursorIndex:preStr.length - 1,
  142. });
  143. }
  144. }
  145. }
  146. else { //光标在字符串最后
  147. let lastChar = this.state.inputValue.charAt(currentTextLength - 1);
  148. if (lastChar === ']'){
  149. let castArray = this.state.inputValue.match(emojiReg);
  150. if(castArray){
  151. let cast = castArray[castArray.length - 1];
  152. lastText = this.state.inputValue.substring(0,this.state.inputValue.length - cast.length);
  153. this.setState({
  154. cursorIndex:this.state.inputValue.length - cast.length,
  155. });
  156. }
  157. else{
  158. lastText = this.state.inputValue.substring(0,this.state.inputValue.length - 1);
  159. this.setState({
  160. cursorIndex:this.state.inputValue.length - 1,
  161. });
  162. }
  163. }
  164. else {
  165. lastText = this.state.inputValue.substring(0,currentTextLength - 1);
  166. this.setState({
  167. cursorIndex:currentTextLength - 1,
  168. });
  169. }
  170. }
  171. }
  172. else {
  173. if (this.state.cursorIndex >= currentTextLength) {
  174. lastText = this.state.inputValue + EMOTIONS_ZHCN[code];
  175. this.setState({
  176. cursorIndex:lastText.length
  177. });
  178. }
  179. else {
  180. let preTemp = this.state.inputValue.substring(0,this.state.cursorIndex);
  181. let nextTemp = this.state.inputValue.substring(this.state.cursorIndex,currentTextLength);
  182. lastText = preTemp + EMOTIONS_ZHCN[code] + nextTemp;
  183. this.setState({
  184. cursorIndex:this.state.cursorIndex + EMOTIONS_ZHCN[code].length
  185. });
  186. }
  187. }
  188. this.setState({
  189. inputValue:lastText,
  190. });
  191. this._onInputChangeText(lastText);
  192. }
  193. _onSelectionChange(event){
  194. this.setState({
  195. cursorIndex:event.nativeEvent.selection.start,
  196. });
  197. }
  198. _onInputChangeText(text){
  199. //设值
  200. this.setState({
  201. inputValue:text,
  202. });
  203. //改变按钮颜色
  204. if (text !== '' && text.length > 0){
  205. this.refs.sendBtnWrapper.setNativeProps({
  206. style:{
  207. backgroundColor:'#56b2f0'
  208. },
  209. });
  210. this.refs.sendBtnText.setNativeProps({
  211. style:{
  212. color:'#1d1d1d'
  213. },
  214. });
  215. }
  216. else {
  217. this.refs.sendBtnWrapper.setNativeProps({
  218. style:{
  219. backgroundColor:'#f5f5f5'
  220. }
  221. });
  222. this.refs.sendBtnText.setNativeProps({
  223. style:{
  224. color:'#bbbbbb',
  225. }
  226. });
  227. }
  228. }
  229. _onFocus(){
  230. this.setState({
  231. isEmotionsVisible:false,
  232. });
  233. }
  234. _onSendMsg(){
  235. this.setState({
  236. tempSendTxtArray:[],
  237. });
  238. let finalMsg = '';
  239. if (this.state.inputValue !== '' && this.state.inputValue.length > 0) {
  240. this._matchContentString(this.state.inputValue);
  241. for (let i = 0; i < this.state.tempSendTxtArray.length; i++){
  242. finalMsg += this.state.tempSendTxtArray[i];
  243. }
  244. this._onInputChangeText('');
  245. this.props.onSend(finalMsg);
  246. }
  247. }
  248. render() {
  249. if (Platform.OS === 'android'){
  250. return this._renderAndroidView();
  251. }
  252. return this._renderIosView();
  253. }
  254. _renderAndroidView(){
  255. return <ModalBox
  256. swipeToClose={false}
  257. backdropOpacity={0}
  258. backButtonClose={true}
  259. onClosed={() => this._onModalBoxClosed()}
  260. style={[styles.container]} position={'bottom'} ref={'modal'}>
  261. <TouchableWithoutFeedback onPress={() => this.closeInputBar()}>
  262. <View style={styles.box_container}/>
  263. </TouchableWithoutFeedback>
  264. <View style={styles.inputContainer}>
  265. <View style={styles.textContainer}>
  266. <TouchableWithoutFeedback
  267. onPress={() => this._toogleShowEmojiView()}>
  268. <Image style={styles.emojiStyle} source={require('./emotions/ic_emoji.png')}/>
  269. </TouchableWithoutFeedback>
  270. <TextInput
  271. ref="textInput"
  272. style={styles.inputStyle}
  273. underlineColorAndroid="transparent"
  274. multiline = {true}
  275. autoFocus={true}
  276. editable={true}
  277. placeholder={'说点什么'}
  278. placeholderTextColor={'#bababf'}
  279. onSelectionChange={(event) => this._onSelectionChange(event)}
  280. onChangeText={(text) => this._onInputChangeText(text)}
  281. onFocus={() => this._onFocus()}
  282. defaultValue={this.state.inputValue}/>
  283. <TouchableWithoutFeedback onPress={() => this._onSendMsg()}>
  284. <View ref="sendBtnWrapper" style={[styles.sendBtnStyle]}>
  285. <Text ref="sendBtnText" style={[styles.sendBtnTextStyle]}>发送</Text>
  286. </View>
  287. </TouchableWithoutFeedback>
  288. </View>
  289. <Line lineColor={'#bababf'}/>
  290. {
  291. this.state.isEmotionsVisible &&
  292. <EmotionsView onSelected={(code) => this._onEmojiSelected(code)}/>
  293. }
  294. </View>
  295. </ModalBox>;
  296. }
  297. _renderIosView(){
  298. return <Modal
  299. animationType={'slide'} transparent={true} visible={this.state.modalVisible}>
  300. <TouchableWithoutFeedback onPress={() => this.closeInputBar()}>
  301. <View style={styles.box_container}/>
  302. </TouchableWithoutFeedback>
  303. <KeyboardAvoidingView behavior={'position'}>
  304. <View style={styles.inputContainer}>
  305. <View style={styles.textContainer}>
  306. <TouchableWithoutFeedback
  307. onPress={() => this._toogleShowEmojiView()}>
  308. <Image style={styles.emojiStyle} source={require('./emotions/ic_emoji.png')}/>
  309. </TouchableWithoutFeedback>
  310. <TextInput
  311. ref="textInput"
  312. style={styles.inputStyle}
  313. underlineColorAndroid="transparent"
  314. multiline = {true}
  315. autoFocus={true}
  316. editable={true}
  317. keyboardType={'default'}
  318. selectionColor={'#56b2f0'}
  319. returnKeyType={'send'}
  320. placeholder={'说点什么'}
  321. enablesReturnKeyAutomatically={true}
  322. placeholderTextColor={'#bababf'}
  323. onSelectionChange={(event) => this._onSelectionChange(event)}
  324. onChangeText={(text) => this._onInputChangeText(text)}
  325. onFocus={() => this._onFocus()}
  326. defaultValue={this.state.inputValue}/>
  327. <TouchableWithoutFeedback onPress={() => this._onSendMsg()}>
  328. <View ref="sendBtnWrapper" style={[styles.sendBtnStyle]}>
  329. <Text ref="sendBtnText" style={[styles.sendBtnTextStyle]}>发送</Text>
  330. </View>
  331. </TouchableWithoutFeedback>
  332. </View>
  333. <Line lineColor={'#bababf'}/>
  334. {
  335. this.state.isEmotionsVisible &&
  336. <EmotionsView onSelected={(code) => this._onEmojiSelected(code)}/>
  337. }
  338. </View>
  339. </KeyboardAvoidingView>
  340. </Modal>;
  341. }
  342. }
  343. Line.defaultProps = {
  344. isVisible: false,
  345. };
  346. ChatInputBar.propTypes = {
  347. onSend:PropTypes.func, //返回text文本
  348. isVisible:PropTypes.bool,
  349. };
  350. const styles = StyleSheet.create({
  351. container: {
  352. width:'100%',
  353. height:235,
  354. backgroundColor:'transparent',
  355. },
  356. box_container: {
  357. flex:1,
  358. },
  359. inputContainer: {
  360. width:'100%',
  361. position:'absolute',
  362. bottom:0,
  363. },
  364. textContainer: {
  365. width:'100%',
  366. height:48,
  367. flexDirection:'row',
  368. backgroundColor:'white',
  369. alignItems:'center',
  370. },
  371. outside: {
  372. flex:1,
  373. width:'100%',
  374. },
  375. emojiStyle:{
  376. height:28,
  377. width:28,
  378. marginLeft:10,
  379. },
  380. inputStyle:{
  381. flex:1,
  382. paddingTop:8,
  383. paddingBottom:8,
  384. paddingLeft:10,
  385. paddingRight:10,
  386. height:32,
  387. marginLeft:10,
  388. marginRight:10,
  389. backgroundColor:'#f5f5f5',
  390. borderWidth:0,
  391. borderRadius:20,
  392. fontSize:15,
  393. },
  394. sendBtnTextStyle:{
  395. fontSize:15,
  396. color:'#bbbbbb'
  397. },
  398. sendBtnStyle:{
  399. height:32,
  400. width:62,
  401. justifyContent:'center',
  402. alignItems:'center',
  403. marginRight:10,
  404. borderRadius:15,
  405. backgroundColor:'#f5f5f5'
  406. },
  407. });