import 'dart:async'; import 'dart:convert'; import 'dart:math'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:sport/bean/image.dart' as photo; import 'package:sport/bean/message.dart'; import 'package:sport/bean/post.dart'; import 'package:sport/bean/post_user.dart'; import 'package:sport/bean/user_info.dart'; import 'package:sport/db/message_db.dart'; import 'package:sport/pages/social/post_detail_page.dart'; import 'package:sport/pages/social/share_webview.dart'; import 'package:sport/pages/social/user_detail_page.dart'; import 'package:sport/pages/my/feedback_page.dart'; import 'package:sport/provider/lib/view_state_model.dart'; import 'package:sport/provider/message_model.dart'; import 'package:sport/provider/user_model.dart'; import 'package:sport/router/navigator_util.dart'; import 'package:sport/services/api/inject_api.dart'; import 'package:sport/services/api/resp.dart'; import 'package:sport/services/userid.dart'; import 'package:sport/utils/DateFormat.dart'; import 'package:sport/utils/toast.dart'; import 'package:sport/widgets/appbar.dart'; import 'package:sport/widgets/decoration.dart'; import 'package:sport/widgets/loading.dart'; import 'package:sport/widgets/menu_bar.dart'; import 'package:sport/widgets/space.dart'; import 'package:sport/widgets/dialog/popupmenu.dart' as menu; import 'package:provider/provider.dart'; import 'gallery_photo_view.dart'; final List avatarList = [ "https://wx3.sinaimg.cn/mw1024/a6bdcd78gy1gfna8yznv5j20m90m9tbz.jpg", "https://wx2.sinaimg.cn/mw1024/a6bdcd78gy1gfna8x3nbzj20m90m943a.jpg", "https://wx3.sinaimg.cn/mw1024/a6bdcd78gy1gfnaavrha3j20m90m9dkj.jpg", "https://wx4.sinaimg.cn/mw1024/a6bdcd78gy1gfna8znb76j20m90m978t.jpg", "https://wx4.sinaimg.cn/mw1024/a6bdcd78gy1gfna8wnwquj20lr0lradp.jpg", "https://wx4.sinaimg.cn/mw1024/a6bdcd78gy1gfna90vpgkj20lr0lrtby.jpg", "https://wx1.sinaimg.cn/mw1024/a6bdcd78gy1gfna8y4jb6j20m90m9tcj.jpg", "https://wx3.sinaimg.cn/mw1024/a6bdcd78gy1gfnaawemhtj20m90m9q7n.jpg", "https://wx4.sinaimg.cn/mw1024/a6bdcd78gy1gfnaaveqmwj20ls0lsn07.jpg", "https://wxt.sinaimg.cn/mw1024/a6bdcd78gy1gfnaat7uw5j20ls0lr0w0.jpg", "https://wx2.sinaimg.cn/mw1024/a6bdcd78gy1gfnaaubdhhj20m90m9786.jpg", ]; class ChatPage extends StatefulWidget { final UserInfo user; final Post post; // 分享来的帖子 final String hash; // 分享来的link final String image; // 分享来的图片 ... ChatPage(this.user, {this.post, this.hash, this.image}); @override State createState() => _ChatPageState(); } // 服务端的数据 只有 拿了 和 没拿 ,客户端 就是 读了 和没读 ... class _ChatPageState extends State with InjectLoginApi, InjectApi, UserId, WidgetsBindingObserver { GetMenuController _menuController = new GetMenuController(); // ScrollController scrollMenuController = new ScrollController(); StreamSubscription _streamSubscription; List messageList = []; // String othersAvatarUrl = ""; GlobalKey SCROLLVIEW = new GlobalKey(); double emptyHeight; dispose() { super.dispose(); _streamSubscription?.cancel(); } initState() { super.initState(); WidgetsBinding.instance.addObserver(this); // 监听一手自己... initMessageList(); initListen(); // 开启监听... 先试试... addPost(); // 这里是分享过来的 ... // initScrollBottom(); // print("${Provider.of(context, listen: false).user.toJson()}"); // _menuController.scroll(); } // 这个是初始化 聊天列表的... initMessageList() async { // await Future.delayed(Duration(milliseconds: 1000)); // List messageList = []; List data = []; var list = await MessageDB().getMessageForUserId(widget.user.id); int day = -1; for (var item in list) { MessageItem _item = MessageItem.fromJson(item); if (_item.day != day) { day = _item.day; data.add(new MessageItem( messageType: "time", dateTime: DateFormat.format(DateTime.parse(_item.message.createdAt)))); } data.add(_item); } // 读过就操作一手... await MessageDB().updateStatus(widget.user.id); // messageList.addAll(data); int cout = 0; data.forEach((element) { messageList.insert(0, element); }); // 这里每次都会 拿最新的 而不是添加... setState(() { messageList = messageList; }); // SchedulerBinding.instance.addPostFrameCallback((_) { ////here the sublist is already build // scrollMenuController.jumpTo(scrollMenuController.position.maxScrollExtent); // print("2222222222222222222222222"); // print("${scrollMenuController.position.minScrollExtent} - ${scrollMenuController.position.maxScrollExtent}"); // }); _menuController.scroll(); } Future addPost() async { // 这里是拿到了 帖子 if (widget.post != null) { // 拿到后还得 存 MessageInstance _instance = (await api.shareForwardSubject( int.parse(widget.post.id), widget.user.id)) .data; print( "[_instance]${_instance.toJson()}---------------------------------------"); await MessageDB().insert(new MessageItem( message: _instance, status: 0, curId: 0, userId: widget.user.id)); addMessageToPage(_instance); } // 也可能是拿到了 link else if (widget.hash != null) { MessageInstance _instance = (await api.getshareForwardSport(widget.hash, widget.user.id)).data; await MessageDB().insert(new MessageItem( message: _instance, status: 0, curId: 0, userId: widget.user.id)); addMessageToPage(_instance); } else if (widget.image != null) { MessageInstance _instance = (await api.postChatSend( widget.user.id, "image", '{"url":"${widget.image}"}')) .data; await MessageDB().insert(new MessageItem( message: _instance, status: 0, curId: 0, userId: widget.user.id)); addMessageToPage(_instance); } } // 在本页中如果收到了就 ... initListen() { Stream> queryStream = Provider.of(context, listen: false).queryStream; _streamSubscription = queryStream.listen((List list) async { for (MessageInstance item in list) { addMessageToPage(item); } await MessageDB().updateStatus(widget.user.id); }); } // // initScrollBottom() { // _timer = Timer(Duration.zero, () { // _menuController.scrollToBottom(context, SCROLLVIEW, true); // }); // } MessageItem instanceToItem(MessageInstance _instance) { int id = _instance.fromUser.id == int.parse(selfId) ? _instance.toUser.id : _instance.fromUser.id; // 里面收到 直接已读... 好像curId 可以不加 status 也可以不写 ... MessageItem item = new MessageItem(message: _instance, curId: 0, status: 0, userId: id); return item; } // 各种来自别的地方收到的消息 输出到 页面上 addMessageToPage(MessageInstance _instance) { MessageItem item = instanceToItem(_instance); // messageList.insert(0, item); // messageList.add(item); messageList.insert(0, item); // 到头插... setState(() { messageList = messageList; }); } // 封装的聊天msg // @who 判断是用户自己的还是 聊的那个人 // @msg 信息 聊天的那个 // @type 聊天的类型 可能是通过分享过来的那种 ? 游戏 或者是 别的 社区 或者是 链接 ? Widget _buildChatItem(MessageItem item) { // type 是时间 还是 消息... print("${item.toJson()}----------------------------------"); if (item.messageType != null) { return Padding( padding: EdgeInsets.symmetric(vertical: 5.0), child: Center( child: Text( "${item.dateTime}", style: TextStyle(fontSize: 12.0), ), ), ); } GlobalKey anchorKey = GlobalKey(); MessageInstance data = item.message; int who = data.fromUser.id == Provider.of(context, listen: false).user.id ? 1 : 0; String userAvatar = data.fromUser.avatar; Widget chatItemOfType(String type) { List images = []; images.add(photo.Image(id: "1", src: data.data.url)); // 文本或者是图片... if (type == "text" || type == "image") { return Column( children: [ if (data.data.text != null) Text( "${data.data.text}", style: Theme.of(context) .textTheme .subtitle1 .copyWith(fontSize: 16, color: Colors.black), ), if (data.data.url != null) InkWell( child: Container( constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height, maxWidth: MediaQuery.of(context).size.width, ), child: CachedNetworkImage( imageUrl: data.data.url + "?x-oss-process=image/resize,p_10", height: 100, ), ), onTap: () { Navigator.push( context, FadeRoute( page: GalleryPhotoViewWrapper( galleryItems: images, backgroundDecoration: const BoxDecoration( color: Colors.black, ), initialIndex: 1, scrollDirection: Axis.horizontal, loadingBuilder: (_, __) => RequestLoadingWidget(), ), ), ); }, ) ], crossAxisAlignment: CrossAxisAlignment.start, ); } // 论坛消息 ... if (type == "forum-forward") { print("[forum-forward]${item.toJson()}----------------------"); return InkWell( child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ ClipRRect( child: Image.asset( "lib/assets/img/chat.png", width: 40, height: 40, fit: BoxFit.cover, ), borderRadius: BorderRadius.circular(10), ), Space( width: 10.0, ), Expanded( child: RichText( text: TextSpan(children: [ TextSpan( text: "@" + data.data.user.name + ":", style: Theme.of(context) .textTheme .headline6 .copyWith(color: Color(0xFFFFC400))), TextSpan( text: "分享了一篇帖子", style: Theme.of(context).textTheme.subtitle1), ]), ), ) ], ), onTap: () async { Post post = (await api.getPostDetail("${data.data.subject.id}")).data; NavigatorUtil.goPage( context, (context) => PostDetailPage(post, false, null)); }, ); } // 链接.. if (type == "share") { return InkWell( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ CachedNetworkImage( imageUrl: avatarList[8], width: 60.0, height: 60.0, ), Space( width: 5.0, ), Expanded( child: RichText( text: TextSpan(children: [ TextSpan( text: data.fromUser.name, style: Theme.of(context) .textTheme .headline6 .copyWith(color: Color(0xFFFFC400))), TextSpan( text: "分享了他的运动记录,快来围观吧~", style: Theme.of(context).textTheme.subtitle1), ]), ), ) ]), onTap: () async { await NavigatorUtil.goPage( context, (context) => WebViewSharePage( data.data.url, hash: data.data.share.hash, )); }, ); } } Widget chatContent(){ return ConstrainedBox( constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.6), child: Container( padding: who == 1 ? EdgeInsets.fromLTRB(12, 6, 20, 8) : EdgeInsets.fromLTRB(20, 6, 12, 8), key: anchorKey, child: GestureDetector( behavior: HitTestBehavior.opaque, onLongPressStart: (e) { RenderBox renderBox = anchorKey.currentContext.findRenderObject(); var offset = renderBox .localToGlobal(Offset(0.0, renderBox.size.height)); final RelativeRect position = RelativeRect.fromLTRB( e.globalPosition.dx, //取点击位置坐弹出x坐标 offset.dy, //取text高度做弹出y坐标(这样弹出就不会遮挡文本) e.globalPosition.dx, offset.dy); PopupMenuEntry menuItem( {String imgUrl, String text, Function callBack}) => menu.PopupMenuItem( child: InkWell( onTap: () { callBack(); Navigator.pop(context); }, child: Row( mainAxisSize: MainAxisSize.min, children: [ Image.asset( "lib/assets/img/$imgUrl", width: 24, ), SizedBox( width: 4, ), Text( text, ) ], ), ), ); showMenu( context: context, position: position, items: [ PopupMenuItem( child: Container( child: Column( children: [ menuItem( imgUrl: "linkpop_icon_copy.png", text: "复制", callBack: () { Clipboard.setData(ClipboardData( text: '${data.data.text}')); ToastUtil.show("复制成功"); }), who == 1 ? menuItem( imgUrl: "linkpop_icon_del.png", text: "删除", callBack: () async { await MessageDB() .deleteMessageIdMessage( item.messageId); messageList.remove(item); setState(() { messageList = messageList; }); }, ) : menuItem( imgUrl: "linkpop_icon_modify_1.png", text: "举报", callBack: () async { await api.postForumReport( userId: item.userId, content: "该用户涉嫌发送不良消息内容为:${item.message.data.text}"); ToastUtil.show("举报已受理..."); }), menuItem( imgUrl: "linkpop_icon_cancel.png", text: "取消") ], ), )) ], ); }, child: chatItemOfType(data.type))), ); } Widget customPoint(){ print("[data.type]:${data.type}--------------------"); if(data.type != "image"){ return CustomPaint( painter: who == 1 ? BubblePainterRight() : BubblePainter(), child: chatContent()); } return chatContent(); }; Widget spaceItem = Space( width: 12, ); String avatarUrl = data.fromUser?.id == int.parse(selfId) ? Provider.of(context, listen: false).user?.avatar == null ? avatarList[4] : Provider.of(context, listen: false).user?.avatar : widget.user?.avatar == null ? data.fromUser.avatar : widget.user?.avatar; Widget avatar = InkWell( onTap: () async { await NavigatorUtil.goPage( context, (context) => UserDetailPage(PostUser( id: "${data.fromUser?.id}", name: data.fromUser?.name, avatar: avatarUrl))); }, child: CircleAvatar( backgroundImage: CachedNetworkImageProvider(avatarUrl), radius: 20, ), ); List chatContentUsr = [customPoint(), spaceItem, avatar]; List chatContentOther = [avatar, spaceItem, customPoint()]; return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0), child: Row( mainAxisAlignment: who == 1 ? MainAxisAlignment.end : MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: who == 1 ? chatContentUsr : chatContentOther, )); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: InkWell( child: Text( "${widget.user.name}${widget.user.id}", style: titleStyle, ), onTap: (){ NavigatorUtil.goPage(context, (context) => UserDetailPage(PostUser.fromJson({"id":"${widget.user.id}"}))); }, ), leading: buildBackButton(context), ), // resizeToAvoidBottomInset: false, // 透传MediaQuery 的高度? body: WillPopScope( onWillPop: () async { // 这是分享.... 只能一步一步pop 出去 if (widget.post != null || widget.hash != null || widget.image != null) { Navigator.pop(context, true); return false; } return true; }, child: MenuBar( CustomScrollView( key: SCROLLVIEW, shrinkWrap: true, reverse: true, controller: _menuController.scrollMenuController, // controller: scrollMenuController, slivers: [ if (messageList?.length == 0) SliverToBoxAdapter( child: Container(), ), if (messageList?.length != 0) SliverList( delegate: SliverChildBuilderDelegate((content, index) { // MessageInstance data = messageList[index]; return _buildChatItem(messageList[index]); }, childCount: messageList.length), ), ], ), menuIdentity: new MenuIdentity(menuScene: "chat", userId: widget.user.id), inputField: "", scrollToBottom: _menuController.scroll, // 暂时可以废弃... sendCallBack: addMessageToPage, globalkey: SCROLLVIEW, ))); } }