chat.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. import 'dart:convert';
  2. import 'package:flutter/services.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter/cupertino.dart';
  5. import 'package:fluttertoast/fluttertoast.dart';
  6. import 'package:image_picker/image_picker.dart';
  7. import 'package:twongCustomer/routes/utils.dart';
  8. import 'package:twongCustomer/utils/loading.dart';
  9. import '../utils/http.dart';
  10. import '../utils/cache.dart';
  11. import '../utils/emoji.dart';
  12. import '../models/index.dart';
  13. import '../utils/events.dart';
  14. import '../utils/size_fit.dart';
  15. class ChatPage extends StatefulWidget{
  16. final UserInfo user;
  17. ChatPage(this.user, {Key key}): super(key: key);
  18. @override
  19. State<StatefulWidget> createState() {
  20. return _ChatPageState();
  21. }
  22. }
  23. class _ChatPageState extends State<ChatPage> with WidgetsBindingObserver {
  24. UserInfo user;
  25. FocusNode _inputFocus = new FocusNode();
  26. List<List<String>> emojis = new List<List<String>>();
  27. List<HttpMessage> _messageList = new List<HttpMessage>();
  28. ScrollController _scrollController = new ScrollController();
  29. TextEditingController _textController = new TextEditingController();
  30. @override
  31. void initState() {
  32. super.initState();
  33. WidgetsBinding.instance.addObserver(this);
  34. initData();
  35. initEventListener();
  36. Events.fire(ClearUnread(widget.user.uid));
  37. }
  38. @override
  39. void didChangeAppLifecycleState(AppLifecycleState state) {
  40. print("--" + state.toString());
  41. switch (state) {
  42. case AppLifecycleState.inactive: // 处于这种状态的应用程序应该假设它们可能在任何时候暂停。
  43. break;
  44. case AppLifecycleState.resumed:// 应用程序可见,前台
  45. break;
  46. case AppLifecycleState.paused: // 应用程序不可见,后台
  47. break;
  48. case AppLifecycleState.detached:
  49. break;
  50. }
  51. }
  52. @override
  53. void dispose() {
  54. super.dispose();
  55. print("chat dispose");
  56. }
  57. void initData() {
  58. initEmojis();
  59. user = AppData.get("info");
  60. // Loading.show(context);
  61. Http.getHistory(widget.user.uid).then((list) {
  62. if (!mounted) return;
  63. setState(() {
  64. _messageList = list;
  65. });
  66. Loading.hide(context);
  67. }).catchError((err) {
  68. Loading.hide(context);
  69. Fluttertoast.showToast(msg: "获取聊天记录失败");
  70. print("history err: " + err.toString());
  71. });
  72. }
  73. void initEmojis() {
  74. var idx = 1;
  75. var page = 0;
  76. emojis.add(new List<String>());
  77. for(var item in EmojiConf.list) {
  78. emojis[page].add(item);
  79. if(idx % 24 == 0){
  80. page++;
  81. emojis.add(new List<String>());
  82. }
  83. idx++;
  84. }
  85. }
  86. void initEventListener() {
  87. Events.on<UserMessage>().listen((event) {
  88. if(!mounted) return;
  89. setState(() {
  90. _messageList.add(event.message);
  91. _scrollController.animateTo(0,
  92. duration: const Duration(milliseconds: 300),
  93. curve: Curves.easeOut,
  94. );
  95. });
  96. });
  97. }
  98. @override
  99. Widget build(BuildContext context) {
  100. return Scaffold(
  101. resizeToAvoidBottomInset: true,
  102. appBar: AppBar(
  103. centerTitle: true,
  104. title: Text(widget.user.username),
  105. actions: [
  106. IconButton(icon: Icon(Icons.more_horiz_outlined, size: 34), onPressed: () {
  107. RouterUtils.toUserInfo(context, widget.user);
  108. }),
  109. Container(width: 6)
  110. ],
  111. ),
  112. body: SafeArea(
  113. child: ListView(children: <Widget>[
  114. Container(
  115. height: 500,
  116. child: ListView.builder(
  117. reverse: true,
  118. controller: _scrollController,
  119. physics: BouncingScrollPhysics(),
  120. itemCount: _messageList.length,
  121. itemBuilder: (context, i) {
  122. return _buildMessage(i);
  123. },
  124. ),
  125. ),
  126. // Divider(height: 1.0),
  127. _buildComposer(),
  128. ]),
  129. )
  130. );
  131. }
  132. Widget _buildAvatar(String url) {
  133. return Container(
  134. padding: EdgeInsets.only(top: 4),
  135. child: ClipRRect(
  136. borderRadius: BorderRadius.circular(20),
  137. child: Image.network(url, width: 40, height: 40)
  138. ));
  139. }
  140. Widget _buildMsgBody(bool visitor, HttpMessage msg) {
  141. // print("build msg: " + user.toJson().toString());
  142. return Flexible(child: Container(
  143. margin: EdgeInsets.only(left: 10, right: 10),
  144. child: Column(
  145. crossAxisAlignment: visitor ? CrossAxisAlignment.start : CrossAxisAlignment
  146. .end,
  147. children: [
  148. Text(visitor ? msg.visitor_name : user.name, textAlign: TextAlign.right,),
  149. Container(
  150. margin: EdgeInsets.only(top: 6),
  151. padding: EdgeInsets.all(10),
  152. decoration: BoxDecoration(
  153. color: _getBackground(visitor, msg),
  154. borderRadius: BorderRadius.all(Radius.circular(20))
  155. ),
  156. child: _buildMsgContent(msg)
  157. )
  158. ],
  159. ),
  160. ));
  161. }
  162. Color _getBackground(bool visitor, HttpMessage msg) {
  163. if(msg.content.startsWith("face[")) {
  164. return visitor ? Colors.cyanAccent : Colors.greenAccent;
  165. }
  166. if(msg.content.startsWith("img[")) {
  167. return null;
  168. }
  169. if(msg.content.startsWith("product[")) {
  170. return Colors.white70;
  171. }
  172. if(msg.content.startsWith("order[")) {
  173. return null;
  174. }
  175. return visitor ? Colors.cyanAccent : Colors.greenAccent;
  176. }
  177. Widget _buildMsgContent(HttpMessage msg) {
  178. if(msg.content.startsWith("face[")) {
  179. var name = msg.content.substring(5, msg.content.length - 1);
  180. return Container( height: 32, width: 32, child: Image.asset("assets/emojis/" + name + ".png"));
  181. }
  182. if(msg.content.startsWith("img[")) {
  183. var url = msg.content.substring(4, msg.content.length - 1);
  184. return Image.network(url);
  185. }
  186. if(msg.content.startsWith("product[")) {
  187. var url = msg.content.substring(8, msg.content.length - 1);
  188. print(url);
  189. var product = Product.fromJson(json.decode(url));
  190. return _buildProduct(product);
  191. }
  192. if(msg.content.startsWith("order[")) {
  193. var url = msg.content.substring(6, msg.content.length - 1);
  194. print(url);
  195. var order = Order.fromJson(json.decode(url));
  196. return _buildOrder(order);
  197. }
  198. return Text(msg.content);
  199. }
  200. Widget _buildOrder(Order order) {
  201. return Container(
  202. padding: EdgeInsets.all(10.px),
  203. decoration: BoxDecoration(
  204. color: Colors.white,
  205. borderRadius: BorderRadius.circular(4.px)
  206. ),
  207. child: Column(
  208. crossAxisAlignment: CrossAxisAlignment.start,
  209. children: [
  210. Text("订单号:"),
  211. Row(
  212. children: [
  213. Expanded(child: Text(order.order_id, style: TextStyle(decoration:
  214. TextDecoration.underline, fontWeight: FontWeight.bold))),
  215. GestureDetector(
  216. onTap: () {
  217. Clipboard.setData(ClipboardData(text: order.order_id))
  218. .then((value) => Fluttertoast.showToast(msg: "复制成功!"));
  219. },
  220. child: Container(
  221. padding: EdgeInsets.only(left: 8.px,right: 8.px, top: 2.px, bottom: 2.px),
  222. decoration: BoxDecoration(
  223. color: Colors.blue,
  224. borderRadius: BorderRadius.circular(4.px)
  225. ),
  226. child: Text("复制", style: TextStyle(color: Colors.white)),
  227. ),
  228. )
  229. ],
  230. )
  231. ],
  232. ));
  233. }
  234. Widget _buildProduct(Product product) {
  235. return Container(
  236. padding: EdgeInsets.all(4.px),
  237. child: Column(
  238. children: [
  239. Image.network(product.image),
  240. Text(product.store_name, style: TextStyle(fontWeight: FontWeight.bold)),
  241. Row(
  242. children: [
  243. Expanded(child: RichText(
  244. text: TextSpan(
  245. text: "会员价:",
  246. style: TextStyle(color: Colors.black, fontSize: 14.px),
  247. children: [
  248. TextSpan(
  249. text: "¥",
  250. style: TextStyle(color: Colors.black, fontSize: 12.px),
  251. ),
  252. TextSpan(
  253. text: product.vip_price,
  254. style: TextStyle(color: Colors.red, fontSize: 16.px)
  255. )
  256. ]
  257. ),
  258. )),
  259. Expanded(child: RichText(
  260. text: TextSpan(
  261. text: "售价:",
  262. style: TextStyle(color: Colors.black, fontSize: 14.px),
  263. children: [
  264. TextSpan(
  265. text: "¥",
  266. style: TextStyle(color: Colors.black, fontSize: 12.px),
  267. ),
  268. TextSpan(
  269. text: product.price,
  270. style: TextStyle(color: Colors.red, fontSize: 16.px)
  271. )
  272. ]
  273. ),
  274. )),
  275. ],
  276. )
  277. ],
  278. ),
  279. );
  280. }
  281. Widget _buildMessage(idx) {
  282. var msg = _messageList[_messageList.length - idx - 1];
  283. var visitor = msg.mes_type == "visitor";
  284. var customer = AppData.get("info") as UserInfo;
  285. // print(msg.toJson());
  286. return Container(
  287. padding: EdgeInsets.all(10),
  288. alignment: visitor ? Alignment.topLeft : Alignment.topRight,
  289. child: visitor ? Row(
  290. crossAxisAlignment: CrossAxisAlignment.start,
  291. mainAxisAlignment: MainAxisAlignment.start,
  292. children: [
  293. _buildAvatar(visitor ? msg.visitor_avator : customer.avator),
  294. _buildMsgBody(visitor, msg)
  295. ],
  296. ): Row(
  297. crossAxisAlignment: CrossAxisAlignment.start,
  298. mainAxisAlignment: MainAxisAlignment.end,
  299. children: [
  300. _buildMsgBody(visitor, msg),
  301. _buildAvatar(customer.avator)
  302. ],
  303. )
  304. );
  305. }
  306. Widget _buildComposer() {
  307. return Container(
  308. alignment: Alignment.center,
  309. height: 45.0,
  310. width: double.infinity,
  311. margin: EdgeInsets.all(3.0),
  312. child: Row(
  313. children: <Widget>[
  314. IconButton(icon: Icon(
  315. Icons.emoji_emotions, color: Colors.black26, size: 30,),
  316. onPressed: _openFaces),
  317. Expanded(
  318. child: TextField(
  319. focusNode: _inputFocus,
  320. textInputAction: TextInputAction.send,
  321. controller: _textController,
  322. onSubmitted: _submitMsg,
  323. decoration: InputDecoration(
  324. contentPadding: EdgeInsets.all(10.0),
  325. hintText: "想说点儿什么?",
  326. fillColor: Colors.white,
  327. filled: true,
  328. border: OutlineInputBorder(
  329. borderSide: BorderSide.none,
  330. borderRadius: BorderRadius.all(Radius.circular(5)),
  331. ),
  332. ),
  333. ),
  334. ),
  335. IconButton(
  336. icon: Icon(Icons.add_circle_outline),
  337. onPressed: _openAction,
  338. color: Colors.black26,
  339. ),
  340. ],
  341. ),
  342. );
  343. }
  344. void _submitMsg(String txt) async {
  345. var tem = _textController.text;
  346. _textController.text = "";
  347. FocusScope.of(context).requestFocus(_inputFocus);
  348. await Http.sendMsg(widget.user.uid, txt).then((res) {
  349. var msg = HttpMessage();
  350. msg.content = txt;
  351. Events.fire(UserMessage(msg));
  352. }).catchError((err) {
  353. print(err);
  354. _textController.text = tem;
  355. Fluttertoast.showToast(msg: "发送失败!");
  356. });
  357. }
  358. void _submitFace(String name) async {
  359. var code = "face[$name]";
  360. await Http.sendMsg(widget.user.uid, code).then((res) {
  361. var msg = HttpMessage();
  362. msg.content = code;
  363. Events.fire(UserMessage(msg));
  364. Navigator.pop(context);
  365. }).catchError((err) {
  366. print(err);
  367. Fluttertoast.showToast(msg: "发送失败!");
  368. });
  369. }
  370. Widget _getAvatar(String name) {
  371. return InkWell(onTap: () => _submitFace(name), child: CircleAvatar(child:
  372. Container(child: Image.asset("assets/emojis/" + name + ".png"),
  373. margin: EdgeInsets.all(8)), radius: 20, backgroundColor: Colors.transparent));
  374. }
  375. Future _openFaces () {
  376. var pageWidgets = List<Widget>();
  377. for(var item in emojis) {
  378. var faceWidgets = List<Widget>();
  379. for(var face in item) {
  380. faceWidgets.add(_getAvatar(face));
  381. }
  382. pageWidgets.add(GridView(
  383. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  384. crossAxisCount: 8, //横轴三个子widget
  385. childAspectRatio: 1 //宽高比为1时,子widget
  386. ),
  387. children: faceWidgets,
  388. ));
  389. }
  390. return showModalBottomSheet(
  391. context: context,
  392. isScrollControlled: true,
  393. builder: (BuildContext context) {
  394. return Container(
  395. height: 160,
  396. child: PageView(
  397. children: pageWidgets,
  398. ),
  399. );
  400. });
  401. }
  402. Future _openAction() {
  403. return showModalBottomSheet(
  404. context: context,
  405. builder: (BuildContext context) {
  406. return SafeArea(child: Column(
  407. mainAxisSize: MainAxisSize.min,
  408. children: <Widget>[
  409. new ListTile(
  410. leading: Icon(Icons.photo_camera),
  411. title: Text("相机拍摄"),
  412. onTap: () async {
  413. var image = await ImagePicker.pickImage(
  414. source: ImageSource.camera);
  415. Navigator.pop(context);
  416. },
  417. ),
  418. new ListTile(
  419. leading: Icon(Icons.photo_library),
  420. title: Text("照片库"),
  421. onTap: () async {
  422. var image = await ImagePicker.pickImage(
  423. source: ImageSource.gallery);
  424. Navigator.pop(context);
  425. },
  426. ),
  427. ],
  428. ));
  429. });
  430. }
  431. }