Hibok
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

641 rivejä
22 KiB

  1. import 'dart:io';
  2. import 'package:cached_network_image/cached_network_image.dart';
  3. import 'package:chat/chat/translate_state.dart';
  4. import 'package:chat/data/UserData.dart';
  5. import 'package:chat/data/chat_data_mgr.dart';
  6. import 'package:chat/data/constants.dart';
  7. import 'package:chat/generated/i18n.dart';
  8. import 'package:chat/models/ChatMsg.dart';
  9. import 'package:chat/models/UserInfo.dart';
  10. import 'package:chat/models/keyboard_provider.dart';
  11. import 'package:chat/models/ref_name_provider.dart';
  12. import 'package:chat/models/voucher_change.dart';
  13. import 'package:chat/proto/all.pbserver.dart';
  14. import 'package:chat/utils/CustomUI.dart';
  15. import 'package:chat/utils/HttpUtil.dart';
  16. import 'package:chat/utils/MessageMgr.dart';
  17. import 'package:chat/utils/TokenMgr.dart';
  18. import 'package:chat/utils/analyze_utils.dart';
  19. import 'package:chat/utils/app_navigator.dart';
  20. import 'package:chat/utils/blacklist_mgr.dart';
  21. import 'package:chat/utils/count_down_button.dart';
  22. import 'package:chat/utils/friend_list_mgr.dart';
  23. import 'package:chat/utils/msgHandler.dart';
  24. import 'package:chat/utils/net_state_widget.dart';
  25. import 'package:chat/utils/sound_util.dart';
  26. import 'package:chat/utils/sp_utils.dart';
  27. import 'package:chat/utils/sql_util.dart';
  28. import 'package:dio/dio.dart';
  29. import 'package:extended_text/extended_text.dart';
  30. import 'package:flutter/cupertino.dart';
  31. import 'package:flutter/material.dart';
  32. import 'package:flutter/services.dart';
  33. import 'package:oktoast/oktoast.dart';
  34. import 'package:provider/provider.dart';
  35. import '../r.dart';
  36. import 'ChatPageItem.dart';
  37. import 'input_bar.dart';
  38. import 'package:chat/utils/PopUpMenu.dart' as myPop;
  39. import 'package:chat/models/money_change.dart';
  40. class ChatPage extends StatefulWidget {
  41. final int friendId;
  42. final int enterType; // 0默认 1图片
  43. final dynamic enterContent;
  44. final bool isTranslateButler;
  45. ChatPage({Key key, this.friendId, this.enterType = 0, this.enterContent,this.isTranslateButler=false})
  46. : super(key: key);
  47. _ChatPageState createState() => _ChatPageState();
  48. }
  49. class _ChatPageState extends State<ChatPage> {
  50. ScrollController _scrollCtrl = ScrollController();
  51. MessageMgr msgMgr = MessageMgr();
  52. UserInfo friendInfo;
  53. List<MsgModel> msgList;
  54. KeyboardIndexProvider _keyboardIndexProvider = KeyboardIndexProvider();
  55. TextEditingController nickNameController = new TextEditingController();
  56. //统计聊天时长
  57. int startTime;
  58. @override
  59. void dispose() {
  60. var endTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
  61. AnalyzeUtils.commitChatDuration(startTime, endTime);
  62. MessageMgr().off('Send CoinBag', _sendCoin);
  63. msgMgr.off('New Chat Message', receiveMsg);
  64. msgMgr.off('Keyboard Hide', dealWithKeyboardHide);
  65. msgMgr.off('Delete Select Message', _deleteItem);
  66. MsgHandler.curActiveSession = 0;
  67. SoundUtils().stop();
  68. nickNameController.dispose();
  69. _scrollCtrl.dispose();
  70. super.dispose();
  71. }
  72. @override
  73. void initState() {
  74. super.initState();
  75. print('init chatpage');
  76. getDefaultSetting();
  77. getUserInfo();
  78. startTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
  79. msgList = ChatDataMgr().getRecord();
  80. msgMgr.on('New Chat Message', receiveMsg);
  81. msgMgr.on('Keyboard Hide', dealWithKeyboardHide);
  82. msgMgr.on('Send CoinBag', _sendCoin);
  83. msgMgr.on('Delete Select Message', _deleteItem);
  84. }
  85. void _sendFile(File file) async {
  86. // File file = await FilePicker.getFile();
  87. int fileSize = file.lengthSync();
  88. print('选择的文件 ${file.path} 大小 $fileSize');
  89. if (fileSize > 33 * 1024 * 1024) {
  90. showToast('文件大于33M');
  91. return;
  92. }
  93. var fileName = file.path.split('/').last;
  94. print('fileName $fileName');
  95. var ext = '';
  96. var extList = fileName.split('.');
  97. if (extList.length > 1) {
  98. ext = extList.last;
  99. }
  100. print('ext $ext');
  101. var fileMsg = FileChat.create();
  102. fileMsg.type = ext;
  103. fileMsg.size = fileSize;
  104. fileMsg.name = fileName;
  105. var msg = MsgHandler.createSendMsg(
  106. ChatType.FileChatType, fileMsg.writeToBuffer(),
  107. friendId: widget.friendId,
  108. localFile: file.path,
  109. channelType: ChatChannelType.Session);
  110. sendMsg(msg);
  111. }
  112. _sendCoin(args) async {
  113. var res = await _checkCoinBeforeSend(args['amount']);
  114. if (res != null) {
  115. args['redNo'] = res['redNo'];
  116. args['friendId'] = widget.friendId;
  117. var msg = MsgHandler.createCoinBagMsg(args);
  118. sendMsg(msg);
  119. }
  120. }
  121. //校验
  122. _checkCoinBeforeSend(int amount) async {
  123. Map data = {
  124. "userId": UserData().basicInfo.userId,
  125. "rUserId": friendInfo.userId,
  126. "price": amount,
  127. };
  128. data['sign'] = TokenMgr().getSign(data);
  129. Response res = await HttpUtil().post('red/packet/send', data: data);
  130. if (res == null) {
  131. return null;
  132. }
  133. Map resData = res.data;
  134. if (resData['code'] == 0) {
  135. print(resData['data']);
  136. return resData['data'];
  137. }
  138. return null;
  139. }
  140. void getDefaultSetting() async {
  141. bool soundPlayMode =
  142. (await SPUtils.getBool(Constants.SOUND_PLAY_MODE)) ?? false;
  143. _keyboardIndexProvider.init(soundPlayMode);
  144. }
  145. dealWithKeyboardHide(args) {
  146. if (_keyboardIndexProvider.curKeyboardIndex == 0) {
  147. hideKeyBoard();
  148. }
  149. }
  150. getUserInfo() async {
  151. //先从本地获取用户信息
  152. friendInfo = await HttpUtil().getFriendInfo(widget.friendId, true);
  153. //如果是新的聊天,向服务器发送创建会话消息
  154. if (msgList.length == 0) {
  155. MsgHandler.getActiveSesstion(
  156. [friendInfo.userId, UserData().basicInfo.userId]);
  157. }
  158. if (mounted) {
  159. setState(() {});
  160. }
  161. WidgetsBinding.instance.addPostFrameCallback((_) {
  162. if (widget.enterType == 1) {
  163. print('接收到的:${widget.enterContent}');
  164. _sendFile(File(widget.enterContent));
  165. } else if (widget.enterType == 2) {
  166. //转发消息
  167. MsgModel originMsg = widget.enterContent;
  168. MsgModel msg = MsgHandler.createSendMsg(
  169. ChatType.valueOf(originMsg.msgType), originMsg.msgContent);
  170. msg.extraInfo = originMsg.extraInfo;
  171. msg.extraFile = originMsg.extraFile;
  172. msg.localFile = originMsg.localFile;
  173. msg.friendId = widget.friendId;
  174. if (msg.localFile != null) {
  175. msg.state = MsgState.Uploaded;
  176. }
  177. sendMsg(msg);
  178. }
  179. });
  180. }
  181. @override
  182. Widget build(BuildContext context) {
  183. print('build chatpage');
  184. if (friendInfo == null) {
  185. return Scaffold(
  186. backgroundColor: const Color(0xFFE2E9F1),
  187. body: SafeArea(
  188. child: Center(
  189. child: CircularProgressIndicator(),
  190. ),
  191. ),
  192. );
  193. }
  194. List<Widget> actions = [];
  195. int voucher = Provider.of<VoucherChangeProvider>(context).voucher;
  196. actions.add(Row(
  197. children: <Widget>[
  198. CustomUI.buildImageLabel("assets/images/voucher.png", voucher,
  199. imgOpc: 0.5, imgHeight: 13),
  200. CustomUI.buildImageLabel(
  201. R.assetsImagesCoin, Provider.of<MoneyChangeProvider>(context).money,
  202. isLeft: false)
  203. ],
  204. ));
  205. actions.add(TranslateSateWidget(friendId: friendInfo.userId));
  206. actions.add(Container(
  207. margin: EdgeInsets.only(top: 1),
  208. child: myPop.PopupMenuButton(
  209. icon: Icon(
  210. Icons.more_horiz,
  211. size: 24,
  212. ),
  213. offset: Offset(0, 100),
  214. onSelected: (int index) {
  215. if (index == 2) {
  216. AppNavigator.pushInformUserPage(
  217. context, friendInfo.sex == 1, friendInfo.userId);
  218. } else if (index == 1) {
  219. nickNameController.text = Provider.of<RefNameProvider>(context)
  220. .getRefName(friendInfo.userId, friendInfo.nickName);
  221. var confirm = CustomUI.buildConfirmBotton(
  222. I18n.of(context).determine, () async {
  223. nickNameController.text = nickNameController.text.trim();
  224. if (nickNameController.text == null ||
  225. nickNameController.text.length > 25) {
  226. showToast(I18n.of(context).only1_8);
  227. return;
  228. }
  229. Provider.of<RefNameProvider>(context).changeRefName(
  230. friendInfo.userId, nickNameController.text, () {
  231. Navigator.of(context).pop();
  232. });
  233. });
  234. var tip = Column(
  235. children: <Widget>[
  236. Container(
  237. margin: EdgeInsets.only(top: 20),
  238. child: Text(
  239. I18n.of(context).setRemark,
  240. textScaleFactor: 1.0,
  241. style: TextStyle(
  242. color: Constants.BlackTextColor, fontSize: 16),
  243. ),
  244. ),
  245. Container(
  246. margin: EdgeInsets.only(top: 23, bottom: 25),
  247. decoration: BoxDecoration(
  248. color: Colors.grey[200],
  249. borderRadius: BorderRadius.all(Radius.circular(8))),
  250. child: TextField(
  251. keyboardAppearance: Brightness.light,
  252. controller: nickNameController,
  253. textAlign: TextAlign.center,
  254. textInputAction: TextInputAction.search,
  255. style: TextStyle(
  256. textBaseline: TextBaseline.alphabetic,
  257. fontSize: 14),
  258. decoration: InputDecoration(
  259. hintText: friendInfo.nickName,
  260. hintStyle: TextStyle(fontSize: 12),
  261. filled: true,
  262. contentPadding: EdgeInsets.only(top: 10, bottom: 10),
  263. fillColor: Colors.transparent,
  264. border: InputBorder.none,
  265. ),
  266. maxLines: 1,
  267. inputFormatters: [LengthLimitingTextInputFormatter(15)],
  268. ),
  269. )
  270. ],
  271. );
  272. var content = CustomUI.buildConfirmContent(tip, confirm);
  273. CustomUI.buildTip(context, '', content);
  274. }
  275. },
  276. itemBuilder: (BuildContext context) {
  277. return <myPop.PopupMenuItem<int>>[
  278. myPop.PopupMenuItem(
  279. child: Container(
  280. alignment: Alignment.center,
  281. color: Colors.white,
  282. padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
  283. child: Text(I18n.of(context).anonymous_report,
  284. textScaleFactor: 1.0,
  285. maxLines: 1,
  286. style: TextStyle(
  287. color: Color(AppColors.AppBarColor), fontSize: 12)),
  288. ),
  289. value: 2,
  290. ),
  291. myPop.PopupMenuItem(
  292. child: Container(
  293. decoration: BoxDecoration(
  294. border: Border(
  295. top: BorderSide(width: 1, color: Colors.grey[300])),
  296. color: Colors.white,
  297. ),
  298. alignment: Alignment.center,
  299. padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
  300. child: Text(I18n.of(context).Remark,
  301. textScaleFactor: 1.0,
  302. maxLines: 1,
  303. style: TextStyle(
  304. color: Color(AppColors.AppBarColor), fontSize: 12)),
  305. ),
  306. value: 1,
  307. ),
  308. ];
  309. })));
  310. return MultiProvider(
  311. providers: [
  312. ChangeNotifierProvider(create: (_) => _keyboardIndexProvider),
  313. Provider<bool>.value(value: false),
  314. Provider<int>.value(value: widget.friendId),
  315. ],
  316. child: GestureDetector(
  317. onTap: hideKeyBoard,
  318. child: ExtendedTextSelectionPointerHandler(
  319. ///选择文字,消除弹窗
  320. builder: (states) {
  321. return Listener(
  322. child: Scaffold(
  323. resizeToAvoidBottomInset: false,
  324. backgroundColor: const Color(0xFFE2E9F1),
  325. appBar: AppBar(
  326. title: Text(
  327. '${Provider.of<RefNameProvider>(context).getRefName(friendInfo.userId, friendInfo.nickName)}',
  328. textScaleFactor: 1.0,
  329. style: TextStyle(
  330. color: Constants.BlackTextColor,
  331. fontSize: 16.47),
  332. ),
  333. leading: CustomUI.buildCustomLeading(context),
  334. titleSpacing: -10,
  335. centerTitle: false,
  336. elevation: 1,
  337. actions: actions),
  338. body: SafeArea(
  339. child: Column(
  340. children: <Widget>[
  341. NetStateWidget(),
  342. widget.isTranslateButler?_buildTranslationButler():Container(),
  343. widget.isTranslateButler? _buildServiceCard(true,(){}):Container(),
  344. widget.isTranslateButler? _buildServiceCard(false,(){}):Container(),
  345. Expanded(child: _buildMessageList()),
  346. InputBar(sendMsg: sendMsg),
  347. ],
  348. ))),
  349. behavior: HitTestBehavior.translucent,
  350. onPointerDown: (value) {
  351. for (var state in states) {
  352. if (!state.containsPosition(value.position)) {
  353. //clear other selection
  354. state.clearSelection();
  355. }
  356. }
  357. },
  358. onPointerMove: (value) {
  359. //clear other selection
  360. for (var state in states) {
  361. if (!state.containsPosition(value.position)) {
  362. //clear other selection
  363. state.clearSelection();
  364. }
  365. }
  366. },
  367. );
  368. },
  369. )));
  370. }
  371. Widget _buildTranslationButler() {
  372. bool hasHeadImg = true;
  373. if (friendInfo.headimgurl == null || friendInfo.headimgurl.length == 0) {
  374. hasHeadImg = false;
  375. }
  376. /// 5H币/1分钟
  377. String coinTIme = I18n.of(context).translation_butler_coin_time;
  378. coinTIme = coinTIme.replaceAll('/s1', '5');
  379. coinTIme = coinTIme.replaceAll('/s2', '1');
  380. return Container(
  381. padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
  382. color: Colors.white,
  383. child: Row(
  384. children: <Widget>[
  385. ClipRRect(
  386. borderRadius: BorderRadius.circular(8),
  387. child: hasHeadImg
  388. ? CachedNetworkImage(
  389. imageUrl: friendInfo.headimgurl,
  390. placeholder: (context, url) => Image.asset(
  391. Constants.DefaultHeadImgUrl,
  392. width: 54,
  393. height: 54,
  394. ),
  395. width: 54,
  396. height: 54,
  397. )
  398. : SizedBox(
  399. width: 54,
  400. height: 54,
  401. child: Image.asset(R.assetsImagesDefaultNorAvatar))),
  402. Padding(
  403. padding: EdgeInsets.only(left: 10),
  404. child: Column(
  405. children: <Widget>[
  406. Text(
  407. I18n.of(context).translation_butler,
  408. textScaleFactor: 1.0,
  409. style: TextStyle(
  410. color: AppColors.NewAppbarTextColor, fontSize: 15),
  411. ),
  412. Text(
  413. coinTIme,
  414. textScaleFactor: 1.0,
  415. style: TextStyle(
  416. color: AppColors.NewAppbarTextColor, fontSize: 13),
  417. )
  418. ],
  419. ),
  420. ),
  421. Expanded(
  422. child: Container(
  423. width: double.maxFinite,
  424. child: CountDownButton(
  425. I18n.of(context).translation_butler_end_service,
  426. () {
  427. Navigator.of(context).pop();
  428. },
  429. countDownTime: 60,
  430. align: Alignment.centerRight,
  431. onPress: (){},
  432. ),
  433. // alignment: Alignment(1,0),
  434. )),
  435. ],
  436. ),
  437. );
  438. }
  439. Widget _buildServiceCard(bool isStart, Function callBack) {
  440. return Container(
  441. margin: EdgeInsets.all(10),
  442. child: Card(
  443. elevation: 5,
  444. // 阴影
  445. shape: RoundedRectangleBorder(
  446. borderRadius: BorderRadius.circular(10),
  447. // side: BorderSide(color: Colors.green,width: 25),
  448. ),
  449. child: Padding(padding: EdgeInsets.only(left: 10,right: 10,top: 15,bottom: 15),child: Row(
  450. children: <Widget>[
  451. Column(
  452. crossAxisAlignment: CrossAxisAlignment.start,
  453. children: <Widget>[
  454. Text(
  455. isStart?I18n.of(context).translation_butler_start_service:I18n.of(context).translation_butler_service_end,
  456. textScaleFactor: 1.0,
  457. style: TextStyle(
  458. color: AppColors.NewAppbarTextColor, fontSize: 15),
  459. ),
  460. Text(
  461. isStart?I18n.of(context).translation_butler_start_tips:I18n.of(context).translation_butler_evaluation_tips,
  462. textScaleFactor: 1.0,
  463. style: TextStyle(
  464. color: AppColors.NewAppbarTextColor, fontSize: 13),
  465. )
  466. ],
  467. ),
  468. isStart?Container():Expanded(child: Container(
  469. margin: EdgeInsets.only(left: 15 ),
  470. height: 30,
  471. child: RaisedButton(
  472. color: Color(0xff3875E9),
  473. shape: RoundedRectangleBorder(
  474. borderRadius: BorderRadius.all(Radius.circular(10))),
  475. child: Text(
  476. I18n.of(context).translation_butler_evaluation ,
  477. textScaleFactor: 1.0,
  478. style: TextStyle(color: Colors.white, fontSize: 19),
  479. ),
  480. onPressed:(){
  481. CustomUI.buildTranslationEvaluationDialog(context);
  482. } ),
  483. )),
  484. ],
  485. ),),
  486. ),
  487. );
  488. }
  489. Widget _buildMessageList() {
  490. return Container(
  491. alignment: Alignment.topCenter,
  492. child: msgList.length == 0
  493. ? Padding(
  494. padding: EdgeInsets.all(8),
  495. child: Text(
  496. I18n.of(context).chat_tips,
  497. textAlign: TextAlign.center,
  498. textScaleFactor: 1.0,
  499. style: TextStyle(color: Colors.grey, fontSize: 12),
  500. ))
  501. : NotificationListener(
  502. child: Scrollbar(
  503. child: ListView.builder(
  504. reverse: true,
  505. shrinkWrap: true,
  506. itemCount: msgList.length,
  507. controller: _scrollCtrl,
  508. padding: EdgeInsets.all(8.0),
  509. itemBuilder: _buildItem,
  510. )),
  511. onNotification: (notification) {
  512. if (notification is ScrollNotification) {
  513. // var offset = notification.metrics.pixels;
  514. // print('滚动事件 offset $offset');
  515. }
  516. return true;
  517. },
  518. ),
  519. );
  520. }
  521. hideKeyBoard() {
  522. _keyboardIndexProvider.changeSelectIndex(-1);
  523. }
  524. readOnly() {
  525. _keyboardIndexProvider.changeReadOnlyKey(true);
  526. }
  527. sendMsg(MsgModel msg) {
  528. print('对方是否拉黑你 ${friendInfo.isBlackened}');
  529. if (BlacklistMgr.isBlack(friendInfo.userId)) {
  530. return;
  531. }
  532. if (!friendInfo.isCanStrangerNews &&
  533. !FriendListMgr().isMyFriend(friendInfo.userId)) {
  534. showToast(I18n.of(context).stranger_close_tips);
  535. return;
  536. }
  537. MsgHandler.insertMsgToDB(msg);
  538. MsgHandler.sendChatMsg(msg);
  539. if (mounted) {
  540. setState(() {});
  541. if (_scrollCtrl.hasClients) {
  542. _scrollCtrl.animateTo(0,
  543. duration: new Duration(milliseconds: 500), curve: Curves.ease);
  544. }
  545. }
  546. }
  547. void receiveMsg(args) {
  548. if (mounted) {
  549. setState(() {});
  550. if (_scrollCtrl.hasClients) {
  551. _scrollCtrl.animateTo(0,
  552. duration: new Duration(milliseconds: 500), curve: Curves.ease);
  553. }
  554. }
  555. }
  556. _deleteItem(msg) {
  557. MessageMgr().emit('Cancel Request', msg);
  558. print('#### 开始删除--');
  559. msgList.remove(msg);
  560. setState(() {});
  561. SqlUtil().deleteSigleRecordWith(msg.sessionId, msg.time);
  562. }
  563. Widget _buildItem(BuildContext context, int index) {
  564. var lastMsgTime;
  565. if (index < msgList.length - 1) {
  566. lastMsgTime = msgList[index + 1].time;
  567. }
  568. MsgModel msg = msgList[index];
  569. return ChatPageItem(
  570. key: Key(msg.time.toString()),
  571. msg: msg,
  572. hideKeyboard: readOnly,
  573. friendInfo: friendInfo,
  574. lastMsgTime: lastMsgTime);
  575. }
  576. }