Bladeren bron

feat:1.2版本聊天部分

Primroses 4 jaren geleden
bovenliggende
commit
0691f540bd

+ 112 - 14
lib/bean/message.dart

@@ -1,6 +1,3 @@
-import 'package:sport/bean/forum.dart';
-import 'package:sport/bean/post.dart';
-
 class MessageInstance {
   int fromId;
   int toId;
@@ -11,6 +8,7 @@ class MessageInstance {
   String createdAt;
   MessageUser toUser;
   MessageUser fromUser;
+  String relate;
 
   MessageInstance(
       {this.fromId,
@@ -20,7 +18,9 @@ class MessageInstance {
       this.read,
       this.selfSend,
       this.createdAt,
-      this.toUser});
+      this.toUser,
+      this.fromUser,
+      this.relate});
 
   MessageInstance.fromJson(Map<String, dynamic> json) {
     fromId = json['from_id'];
@@ -36,6 +36,7 @@ class MessageInstance {
     fromUser = json['from_user'] != null
         ? new MessageUser.fromJson(json['from_user'])
         : null;
+    relate = json['relate'];
   }
 
   Map<String, dynamic> toJson() {
@@ -53,8 +54,9 @@ class MessageInstance {
       data['to_user'] = this.toUser.toJson();
     }
     if (this.fromUser != null) {
-      data['from_user'] = this.toUser.toJson();
+      data['from_user'] = this.fromUser.toJson();
     }
+    data['relate'] = this.relate;
     return data;
   }
 }
@@ -63,20 +65,28 @@ class MessageData {
   String text;
   String url;
   String logo;
-  MessageUser user;
-  Forum forum;
-  Post subject;
+  MessagePostUser user;
+  MessageForum forum;
+  MessageSubject subject;
 
-  MessageData(
-      {this.text, this.url, this.logo, this.user, this.forum, this.subject});
+  MessageData({
+    this.text,
+    this.url,
+    this.logo,
+    this.user,
+    this.forum,
+    this.subject,
+  });
 
   MessageData.fromJson(Map<String, dynamic> json) {
     text = json['text'];
     url = json['url'];
     logo = json['logo'];
-    user = json['user'];
-    forum = json['forum"'];
-    subject = json['subject'];
+    user = json['user'] != null ? MessagePostUser.fromJson(json['user']) : null;
+    forum = json['forum'] != null ? MessageForum.fromJson(json['forum']) : null;
+    subject = json['subject'] != null
+        ? MessageSubject.fromJson(json["subject"])
+        : null;
   }
 
   Map<String, dynamic> toJson() {
@@ -85,7 +95,7 @@ class MessageData {
     map['url'] = this.url;
     map['logo'] = this.logo;
     map['user'] = this.user;
-    map['forum"'] = this.forum;
+    map['forum'] = this.forum;
     map['subject'] = this.subject;
     return map;
   }
@@ -231,3 +241,91 @@ class ChatMessageInstance {
     return data;
   }
 }
+
+class ChatOnlineInfo {
+  bool online;
+  String relate;
+  int userId;
+
+  ChatOnlineInfo.fromJson(Map<String, dynamic> json) {
+    online = json['online'];
+    relate = json['relate'];
+    userId = json['id'];
+  }
+
+  Map<String, dynamic> toJson() {
+    final Map<String, dynamic> data = new Map<String, dynamic>();
+    data['online'] = this.online;
+    data['relate'] = this.relate;
+    data['id'] = this.userId;
+    return data;
+  }
+}
+
+class MessageForum {
+  String cover;
+  String name;
+  int id;
+
+  MessageForum.fromJson(Map<String, dynamic> json) {
+    cover = json['cover'];
+    name = json['name'];
+    id = json['id'];
+  }
+
+  Map<String, dynamic> toJson() {
+    final Map<String, dynamic> data = new Map<String, dynamic>();
+    data['cover'] = this.cover;
+    data['name'] = this.name;
+    data['id'] = this.id;
+    return data;
+  }
+}
+
+class MessageSubject {
+  String title;
+  String content;
+  int id;
+  List<String> images;
+  String cover;
+
+  MessageSubject.fromJson(Map<String, dynamic> json) {
+    title = json['title'];
+    content = json['content'];
+    id = json['id'];
+    images = new List<String>();
+    cover = json['cover'];
+    images = [];
+    if (json['images'] != null && json['images'].length > 0) {
+      json['images'].forEach((v) {
+        images.add(v);
+      });
+    }
+  }
+
+  Map<String, dynamic> toJson() {
+    final Map<String, dynamic> data = new Map<String, dynamic>();
+    data['title'] = this.title;
+    data['content'] = this.content;
+    data['images'] = this.images;
+    data['id'] = this.id;
+    data['cover'] = this.cover;
+    return data;
+  }
+}
+
+class MessagePostUser {
+  int id;
+  String name;
+  MessagePostUser({this.id, this.name});
+  MessagePostUser.fromJson(Map<String, dynamic> json) {
+    id = json['id'];
+    name = json['name'];
+  }
+  Map<String, dynamic> toJson() {
+    final Map<String, dynamic> data = new Map<String, dynamic>();
+    data['name'] = this.name;
+    data['id'] = this.id;
+    return data;
+  }
+}

+ 80 - 8
lib/db/message_db.dart

@@ -1,21 +1,44 @@
 import 'dart:async';
+import 'dart:convert';
 
 import 'package:path/path.dart';
+import 'package:sport/bean/message.dart';
 import 'package:sqflite/sqflite.dart';
 
 class MessageItem {
-  String message;
+  MessageInstance message;
   int status;
   int curId;
-  MessageItem({this.message, this.status, this.curId});
+  int userId;
+  int unReadCount; //未读数量...
+  int messageId;
+  MessageItem(
+      {this.message,
+      this.status,
+      this.curId,
+      this.userId,
+      this.unReadCount,
+      this.messageId});
 
   Map<String, dynamic> toJson() {
     final Map<String, dynamic> data = new Map<String, dynamic>();
-    data['message'] = this.message;
+    data['message'] = json.encode(this.message);
     data['status'] = this.status;
     data['curId'] = this.curId;
+    data['userId'] = this.userId;
+//    data['unReadCount'] = this.unReadCount;
+//    data["messageId"] = this.messageId;
     return data;
   }
+
+  MessageItem.fromJson(Map<String, dynamic> map) {
+    message = MessageInstance.fromJson(json.decode(map['message']));
+    status = map['status'];
+    curId = map['curId'];
+    userId = map['userId'];
+//    unReadCount = map['unReadCount'];
+//    messageId = map['messageId'];
+  }
 }
 
 class MessageDB {
@@ -46,9 +69,14 @@ class MessageDB {
     return db;
   }
 
+  // @messageId 本地数据库的id
+  // @status    该条数据是否是已读的
+  // @message   信息
+  // @userId    跟谁聊天的信息 跟谁 跟谁 跟谁 跟谁 ...
   FutureOr<void> _onCreate(Database db, int version) async {
+    print("Create Table");
     await db.execute(
-        'CREATE TABLE $TABLE(messageId INTEGER PRIMARY KEY, status INTEGER ,message TEXT, curId INTEGER)');
+        'CREATE TABLE $TABLE(messageId INTEGER PRIMARY KEY, status INTEGER ,message TEXT, curId INTEGER,userId INTEGER)');
   }
 
   Future<int> insert(MessageItem item) async {
@@ -77,23 +105,67 @@ class MessageDB {
   Future<List<Map<String, dynamic>>> findAll() async {
     var dbClient = await db;
     return await dbClient.rawQuery(
-      'SELECT * FROM $TABLE ORDER BY curId DESC',
+      'SELECT * FROM $TABLE ORDER BY messageId DESC',
     );
   }
 
-  Future<List<Map<String, dynamic>>> updateStatus(int curId) async {
+  // 更新状态...
+  Future<List<Map<String, dynamic>>> updateStatus(int userId) async {
+    var dbClient = await db;
+
+    // status = 0 表示 已读状态... 1: 未读
+    await dbClient.update(
+      TABLE,
+      {"status": 0},
+      // Ensure that the Dog has a matching id.
+      where: "userId = ?",
+      // Pass the Dog's id as a whereArg to prevent SQL injection.
+      whereArgs: [userId],
+    );
+  }
+
+  Future<List<Map<String, dynamic>>> getMessageList() async {
+    var dbClient = await db;
+    return await dbClient.rawQuery(
+        "select * from $TABLE c, (select userId, max(messageId) mid FROM $TABLE group by userId) cc where c.messageId = cc.mid order by messageId desc;");
+  }
+
+  Future<List<Map<String, dynamic>>> getMessageForUserId(int userId) async {
     var dbClient = await db;
-    // status = 1 表示 已读状态...
     return await dbClient.rawQuery(
-      "UPDATE $TABLE SET status = 1 WHERE curId = $curId;",
+      'SELECT * FROM $TABLE WHERE userId = $userId;',
     );
   }
 
+  // 删除数据表格
   Future<int> delete() async {
     var dbClient = await db;
     return await dbClient.delete("$TABLE");
   }
 
+  // 获取还有多少未读的数量
+  Future<List<Map<String, dynamic>>> getMessageUnRead() async {
+    var dbClient = await db;
+    return await dbClient.rawQuery(
+      "select c.*, ccc.cout from $TABLE c, (select userId, max(messageId) mid FROM $TABLE group by userId) cc, " +
+          "(select userId, sum(status) cout  FROM $TABLE group by userId) ccc where c.messageId = cc.mid order by messageId desc;",
+    );
+  }
+
+  // 删除数据库
+  void deleteTable() async {
+    var databasesPath = await getDatabasesPath();
+    String path = join(databasesPath, 'message.db');
+    await deleteDatabase(path);
+    print('删除数据库');
+  }
+
+  Future<int> deleteMessage(int messageId) async {
+    var dbClient = await db;
+    return await dbClient
+        .delete("$TABLE", where: "messageId = ?", whereArgs: [messageId]);
+  }
+
 //  Future<int> delete(int time) async {
 //    var dbClient = await db;
 //    return await dbClient.delete(TABLE, where: "time <= ?", whereArgs: [time]);

+ 1 - 1
lib/pages/home/sport_detail_page.dart

@@ -150,7 +150,7 @@ class _PageState extends State<SportDetailPage> with TickerProviderStateMixin, I
                                 break;
                               case "share":
 //                                ToastUtil.show("分享什么东西~");
-                                  NavigatorUtil.goPage(context, (context)=> WebViewSharePage());
+                                  NavigatorUtil.goPage(context, (context)=> WebViewSharePage("www.baidu.com"));
                                 break;
                             }
                           },

+ 413 - 176
lib/pages/social/chat_page.dart

@@ -1,3 +1,4 @@
+import 'dart:async';
 import 'dart:convert';
 import 'dart:math';
 
@@ -8,16 +9,27 @@ import 'package:flutter/services.dart';
 import 'package:shared_preferences/shared_preferences.dart';
 import 'package:sport/bean/message.dart';
 import 'package:sport/bean/post.dart';
+import 'package:sport/bean/post_user.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/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/print_map.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';
 
 final List<String> avatarList = [
   "https://wx3.sinaimg.cn/mw1024/a6bdcd78gy1gfna8yznv5j20m90m9tbz.jpg",
@@ -33,23 +45,6 @@ final List<String> avatarList = [
   "https://wx2.sinaimg.cn/mw1024/a6bdcd78gy1gfnaaubdhhj20m90m9786.jpg",
 ];
 
-// 不封装的话 就直接这样...
-//class ChatPageModel extends ViewStateModel with InjectApi {
-////  final Future _future;
-////  ChatPageModel(this._future);
-//
-//  // 获取聊天信息
-//  Future getUserChat(int userId) async {
-//    ChatMessage message;
-//    try {
-//      message = (await api.getChatUser(userId)).data;
-//    } catch (e) {
-//      print(e);
-//    }
-//    return message;
-//  }
-//}
-
 class ChatPage extends StatefulWidget {
   final String userName;
   final int userId;
@@ -61,23 +56,401 @@ class ChatPage extends StatefulWidget {
   State<StatefulWidget> createState() => _ChatPageState();
 }
 
-class _ChatPageState extends State<ChatPage> with InjectLoginApi, InjectApi {
+// 服务端的数据 只有 拿了  和 没拿 ,客户端 就是 读了 和没读 ...
+class _ChatPageState extends State<ChatPage>
+    with InjectLoginApi, InjectApi, UserId, WidgetsBindingObserver {
   GetMenuController _menuController = new GetMenuController();
-//  ChatPageModel _chatPageModel = new ChatPageModel();
-  List<MessageInstance> _messageList = []; // 初始化 为空...
+
+  StreamSubscription _streamSubscription;
+  List<MessageItem> messageList = [];
+
+  GlobalKey SCROLLVIEW = new GlobalKey();
+
+  dispose() {
+    super.dispose();
+    _streamSubscription?.cancel();
+  }
 
   initState() {
     super.initState();
+    WidgetsBinding.instance.addObserver(this); // 监听一手自己...
     initMessageList();
+    initListen(); // 开启监听... 先试试...
+    addPost(); // 这里是分享过来的 ...
+//    initScrollBottom();
+//    print("${Provider.of<UserModel>(context, listen: false).user.toJson()}");
   }
 
-  void initMessageList() async {
-    var list = await MessageDB().findLatest();
-    // 这不是是个骚的?
+  // 这个是初始化 聊天列表的...
+  initMessageList() async {
+//    List<MessageItem> messageList = [];
+    List<MessageItem> data = [];
+    var list = await MessageDB().getMessageForUserId(widget.userId);
     for (var item in list) {
-      _messageList.add(json.decode(item["message"]));
+      MessageItem _item = MessageItem.fromJson(item);
+      data.add(_item);
+    }
+    // 读过就操作一手...
+    await MessageDB().updateStatus(widget.userId);
+
+//    messageList.addAll(data);
+    data.forEach((element) {
+      messageList.insert(0, element);
+    });
+
+    // 这里每次都会 拿最新的 而不是添加...
+    setState(() {
+      messageList = messageList;
+    });
+  }
+
+  Future addPost() async {
+    if (widget.post != null) {
+      // 拿到后还得 存
+      MessageInstance _instance = (await api.shareForwardSubject(
+              int.parse(widget.post.id), widget.userId))
+          .data;
+
+      await MessageDB().insert(new MessageItem(
+          message: _instance, status: 0, curId: 0, userId: widget.userId));
+
+      addMessageToPage(_instance);
+    }
+  }
+
+  // 在本页中如果收到了就 ...
+  initListen() {
+    Stream<List<MessageInstance>> queryStream =
+        Provider.of<MessageModel>(context, listen: false).queryStream;
+    _streamSubscription =
+        queryStream.listen((List<MessageInstance> list) async {
+      for (MessageInstance item in list) {
+        addMessageToPage(item);
+      }
+      await MessageDB().updateStatus(widget.userId);
+    });
+  }
+//
+//  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;
+    });
+  }
+
+//  @override
+//  void didChangeMetrics() {
+//    super.didChangeMetrics();
+//    WidgetsBinding.instance.addPostFrameCallback((_) {
+//      // 当前是安卓系统并且在焦点聚焦的情况下
+////      _menuController.scrollToBottom();
+//    });
+//  }
+
+// 封装的聊天msg
+// @who  判断是用户自己的还是 聊的那个人
+// @msg  信息 聊天的那个
+// @type 聊天的类型 可能是通过分享过来的那种 ? 游戏 或者是 别的 社区 或者是 链接 ?
+
+  Widget _buildChatItem(MessageItem item) {
+    GlobalKey anchorKey = GlobalKey();
+
+    MessageInstance data = item.message;
+
+    int who = data.fromUser.id ==
+            Provider.of<UserModel>(context, listen: false).user.id
+        ? 1
+        : 0;
+
+    String userAvatar = data.fromUser.avatar;
+
+    Widget chatItemOfType(String type) {
+      // 文本或者是图片...
+      if (type == "text" || type == "image") {
+        return Column(
+          children: <Widget>[
+            if (data.data.text != null)
+              Text(
+                "${data.data.text}",
+                style: Theme.of(context)
+                    .textTheme
+                    .subtitle1
+                    .copyWith(fontSize: 16, color: Colors.black),
+                key: anchorKey,
+              ),
+            if (data.data.url != null)
+              CachedNetworkImage(imageUrl: data.data.url)
+//            SizedBox(
+//              height: 10.0,
+//            ),
+//          if (imageUrls?.length != null)
+//            GridView.count(
+//                physics: new NeverScrollableScrollPhysics(),
+//                shrinkWrap: true,
+//                padding: EdgeInsets.zero,
+//                crossAxisSpacing: 10.0,
+//                crossAxisCount: imageUrls.length > 3 ? 3 : imageUrls.length,
+//                mainAxisSpacing: 10.0,
+//                children: imageUrls
+//                    .asMap()
+//                    .keys
+//                    .map((i) => CachedNetworkImage(
+//                          imageUrl: imageUrls[i],
+//                          fit: BoxFit.cover,
+//                        ))
+//                    .toList())
+          ],
+          crossAxisAlignment: CrossAxisAlignment.start,
+        );
+      }
+      // 论坛消息 ...
+      if (type == "forum-forward") {
+        return InkWell(
+          child: Column(
+            children: <Widget>[
+              RichText(
+                text: TextSpan(children: [
+                  TextSpan(
+                      text: data.data.user.name,
+                      style: Theme.of(context)
+                          .textTheme
+                          .headline6
+                          .copyWith(color: Color(0xFFFFC400))),
+                  TextSpan(
+                      text: data.data.subject.content,
+                      style: Theme.of(context).textTheme.subtitle1),
+                ]),
+              ),
+              Space(
+                height: 5.0,
+              ),
+              if (data.data.subject.images.length > 0)
+                GridView.count(
+                    physics: new NeverScrollableScrollPhysics(),
+                    shrinkWrap: true,
+                    padding: EdgeInsets.zero,
+                    crossAxisSpacing: 10.0,
+                    crossAxisCount: data.data.subject.images.length > 3
+                        ? 3
+                        : data.data.subject.images.length,
+                    mainAxisSpacing: 10.0,
+                    childAspectRatio: 9 / 6,
+                    children: data.data.subject.images
+                        .asMap()
+                        .keys
+                        .map(
+                          (i) => CachedNetworkImage(
+                            alignment: Alignment.centerLeft,
+                            imageUrl: data.data.subject.images[i],
+                            fit: BoxFit.cover,
+//                        width: data.data.subject.images[i]
+                          ),
+                        )
+                        .toList()),
+              Divider(),
+              Row(
+                children: <Widget>[
+                  Container(
+                    width: 20.0,
+                    height: 20.0,
+                    child: ClipRRect(
+                      borderRadius: BorderRadius.circular(6),
+                      child: CachedNetworkImage(
+                        imageUrl: data.data.forum.cover,
+                      ),
+                    ),
+                  ),
+                  Space(
+                    width: 5,
+                  ),
+                  Text(
+                    data.data.forum.name,
+                  )
+                ],
+              )
+            ],
+          ),
+          onTap: () async {
+            Post post =
+                (await api.getPostDetail("${data.data.subject.id}")).data;
+            print(post);
+            NavigatorUtil.goPage(
+                context, (context) => PostDetailPage(post, false, null));
+          },
+        );
+      }
+      // 链接..
+      if (type == "link") {
+        return InkWell(
+            child: Row(
+                crossAxisAlignment: CrossAxisAlignment.start,
+                children: <Widget>[
+              CachedNetworkImage(
+                imageUrl: data.data.logo,
+                width: 60.0,
+                height: 60.0,
+              ),
+              Space(
+                width: 5.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: data.data.text,
+                        style: Theme.of(context).textTheme.subtitle1),
+                  ]),
+                ),
+              )
+            ]),
+          onTap: () async{
+              await NavigatorUtil.goPage(context, (context) => WebViewSharePage(data.data.url));
+          },
+        );
+      }
     }
-    print("$list---------------------------------------");
+
+    Widget customPoint = CustomPaint(
+        painter: who == 1 ? _BubblePainterRight() : _BubblePainter(),
+        child: 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),
+              child: GestureDetector(
+                  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: <Widget>[
+                                Image.asset(
+                                  "lib/assets/img/$imgUrl",
+                                  width: 24,
+                                ),
+                                SizedBox(
+                                  width: 4,
+                                ),
+                                Text(
+                                  text,
+                                )
+                              ],
+                            ),
+                          ),
+                        );
+
+                    showMenu(
+                      context: context,
+                      position: position,
+                      items: <PopupMenuEntry>[
+                        PopupMenuItem(
+                            child: Container(
+                          child: Column(
+                            children: <Widget>[
+                              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: "删除")
+                                  : menuItem(
+                                      imgUrl: "linkpop_icon_modify_1.png",
+                                      text: "举报"),
+                              menuItem(
+                                  imgUrl: "linkpop_icon_cancel.png", text: "取消")
+                            ],
+                          ),
+                        ))
+                      ],
+                    );
+                  },
+                  child: chatItemOfType(data.type))),
+        ));
+
+    Widget spaceItem = Space(
+      width: 12,
+    );
+
+    Widget avatar = InkWell(
+      onTap: () async {
+        await NavigatorUtil.goPage(
+            context,
+            (context) => UserDetailPage(PostUser(
+                id: "${data.fromUser?.id}",
+                name: data.fromUser?.name,
+                avatar: data.fromUser?.id == selfId
+                    ? Provider.of<UserModel>(context, listen: false).user.avatar
+                    : data.fromUser.avatar)));
+      },
+      child: CircleAvatar(
+        backgroundImage: CachedNetworkImageProvider(userAvatar),
+        radius: 20,
+      ),
+    );
+
+    List<Widget> chatContentUsr = [customPoint, spaceItem, avatar];
+
+    List<Widget> 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
@@ -93,169 +466,33 @@ class _ChatPageState extends State<ChatPage> with InjectLoginApi, InjectApi {
         resizeToAvoidBottomInset: false, // 透传MediaQuery 的高度?
         body: MenuBar(
           CustomScrollView(
+            key: SCROLLVIEW,
             reverse: true,
             controller: _menuController.scrollMenuController,
             slivers: <Widget>[
-              SliverList(
-                delegate: SliverChildBuilderDelegate((content, index) {
-                  return Column(
-                    children: <Widget>[],
-                  );
-                }, childCount: 10),
-              )
+              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.userId),
           inputField: "",
-          scrollToBottom: _menuController.scrollToBottom,
+//          scrollToBottom: _menuController.scrollToBottom, // 暂时可以废弃...
+          sendCallBack: addMessageToPage,
+          globalkey: SCROLLVIEW,
         ));
   }
 }
 
-// 封装的聊天msg
-// @type  判断是用户自己的还是 聊的那个人
-// @msg   信息 聊天的那个
-//
-Widget _buildChatItem(BuildContext context,
-    {int type, String msg, int index, List<String> imageUrls}) {
-  GlobalKey anchorKey = GlobalKey();
-
-  Widget customPoint = CustomPaint(
-      painter: type == 1 ? _BubblePainterRight() : _BubblePainter(),
-      child: ConstrainedBox(
-        constraints:
-            BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.6),
-        child: Container(
-            padding: type == 1
-                ? EdgeInsets.fromLTRB(12, 6, 20, 8)
-                : EdgeInsets.fromLTRB(20, 6, 12, 8),
-            child: GestureDetector(
-              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: <Widget>[
-                            Image.asset(
-                              "lib/assets/img/$imgUrl",
-                              width: 24,
-                            ),
-                            SizedBox(
-                              width: 4,
-                            ),
-                            Text(
-                              text,
-                            )
-                          ],
-                        ),
-                      ),
-                    );
-
-                showMenu(
-                  context: context,
-                  position: position,
-                  items: <PopupMenuEntry>[
-                    PopupMenuItem(
-                        child: Container(
-                      child: Column(
-                        children: <Widget>[
-                          menuItem(
-                              imgUrl: "linkpop_icon_copy.png",
-                              text: "复制",
-                              callBack: () {
-                                Clipboard.setData(ClipboardData(text: '$msg'));
-                                ToastUtil.show("复制成功");
-                              }),
-                          type == 1
-                              ? menuItem(
-                                  imgUrl: "linkpop_icon_del.png", text: "删除")
-                              : menuItem(
-                                  imgUrl: "linkpop_icon_modify_1.png",
-                                  text: "举报"),
-                          menuItem(
-                              imgUrl: "linkpop_icon_cancel.png", text: "取消")
-                        ],
-                      ),
-                    ))
-                  ],
-                );
-              },
-              child: Column(
-                children: <Widget>[
-                  Text(
-                    "$msg",
-                    style: Theme.of(context)
-                        .textTheme
-                        .subtitle1
-                        .copyWith(fontSize: 16, color: Colors.black),
-                    key: anchorKey,
-                  ),
-                  if (imageUrls?.length != null)
-                    SizedBox(
-                      height: 10.0,
-                    ),
-                  GridView.count(
-                      physics: new NeverScrollableScrollPhysics(),
-                      shrinkWrap: true,
-                      padding: EdgeInsets.zero,
-                      crossAxisSpacing: 10.0,
-                      crossAxisCount:
-                          imageUrls.length > 3 ? 3 : imageUrls.length,
-                      mainAxisSpacing: 10.0,
-                      children: imageUrls
-                          .asMap()
-                          .keys
-                          .map((i) => CachedNetworkImage(
-                                imageUrl: imageUrls[i],
-                                fit: BoxFit.cover,
-                              ))
-                          .toList())
-                ],
-                crossAxisAlignment: CrossAxisAlignment.start,
-              ),
-            )),
-      ));
-
-  Widget spaceItem = Space(
-    width: 12,
-  );
-
-  Widget avatar = CircleAvatar(
-    backgroundImage: CachedNetworkImageProvider(avatarList[index % 11]),
-    radius: 20,
-  );
-
-  List<Widget> chatContentUsr = [customPoint, spaceItem, avatar];
-
-  List<Widget> chatContentOther = [avatar, spaceItem, customPoint];
-
-  return Padding(
-      padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0),
-      child: Row(
-        mainAxisAlignment:
-            type == 1 ? MainAxisAlignment.end : MainAxisAlignment.start,
-        crossAxisAlignment: CrossAxisAlignment.start,
-        children: type == 1 ? chatContentUsr : chatContentOther,
-      ));
-}
-
 class _BubblePainter extends CustomPainter {
   final circular = Radius.circular(10);
   final Paint _paint = Paint()..color = Colors.white;

+ 287 - 186
lib/pages/social/message_page.dart

@@ -1,3 +1,4 @@
+import 'dart:async';
 import 'dart:convert';
 
 import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
@@ -17,6 +18,7 @@ import 'package:sport/router/navigator_util.dart';
 import 'package:sport/router/routes.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';
@@ -32,13 +34,12 @@ class MessagePage extends StatefulWidget {
 }
 
 class _PageState extends State<MessagePage>
-    with TickerProviderStateMixin, InjectApi {
+    with TickerProviderStateMixin, InjectApi, UserId {
   TabController _controller;
   Future<List<Notice>> _future;
-  Future<List<MessageInstance>> _chatFuture;
-
-  List<MessageInstance> messageList;
 
+  StreamSubscription _streamSubscription;
+  List<MessageItem> messageList;
   final List<String> avatarList = [
     "https://wx3.sinaimg.cn/mw1024/a6bdcd78gy1gfna8yznv5j20m90m9tbz.jpg",
     "https://wx2.sinaimg.cn/mw1024/a6bdcd78gy1gfna8x3nbzj20m90m943a.jpg",
@@ -59,7 +60,8 @@ class _PageState extends State<MessagePage>
     super.initState();
     _controller = TabController(length: 2, vsync: this);
     _future = _getNotice();
-    _chatFuture = getChatIndex();
+    getChatIndex(); // 初始化chat...
+    initListen();
   }
 
   Future<List<Notice>> _getNotice() async {
@@ -71,25 +73,65 @@ class _PageState extends State<MessagePage>
     return items;
   }
 
-  Future<List<MessageInstance>> getChatIndex() async {
-//    SharedPreferences prefs = await SharedPreferences.getInstance();
-//    String token = prefs.getString("token");
+  // MessageInstance 类型是 服务器请求回来的类型 MessageItem 是本地 存储后的类型...
+  void getChatIndex() async {
+    //    List<ChatMessageInstance> list = (await api.getChatIndex()).results;
+//    var list = await MessageDB().getMessageList();
+    messageList = [];
+    var unReadList = await MessageDB().getMessageUnRead();
+
+//    print("[unReadList]:$unReadList---------------------------------------");
+
+    if (unReadList != null && unReadList.length > 0) {
+      List<int> ids = [];
+      for (var item in unReadList) {
+        ids.add(MessageItem.fromJson(item).userId);
+      }
+
+      List<ChatOnlineInfo> chatInfo = (await api.getChatUserInfo(ids)).results;
 
-//    List<ChatMessageInstance> list = (await api.getChatIndex()).results;
-    var list = await MessageDB().findAll();
-    // 这不是是个骚的?
-    for (var item in list) {
-//      messageList.add(json.decode(item as String));
-      print("[message:]------${item}-------------------------------");
+      // 这不是是个骚的?
+      for (int i = 0; i < unReadList.length; i++) {
+        MessageItem item = MessageItem.fromJson(unReadList[i]);
+        for (int j = 0; j < chatInfo.length; j++) {
+          if (item.userId == chatInfo[j].userId) {
+            if (item.message.fromUser.id == int.parse(selfId)) {
+              item.message.toUser.online = chatInfo[i].online;
+            } else {
+              item.message.fromUser.online = chatInfo[i].online;
+            }
+//            print("[online:]${chatInfo[i].online}------------------------");
+            item.message.relate = chatInfo[i].relate;
+            item.unReadCount = unReadList[i]['cout'];
+          }
+        }
+        messageList.add(item);
+      }
     }
-//    print("$list---------------------------------------");
-    return messageList;
+    setState(() {
+      messageList = messageList;
+    });
+//    return messageList;
+  }
+
+  // 在本页中如果收到了就 ...
+  initListen() {
+    Stream<List<MessageInstance>> queryStream =
+        Provider.of<MessageModel>(context, listen: false).queryStream;
+    _streamSubscription = queryStream.listen((List<MessageInstance> list) {
+//      for (MessageInstance item in list) {
+//        addMessageToPage(item);
+//      }
+      print("[receive:message]:$list-----------------------------");
+      getChatIndex();
+    });
   }
 
   @override
   void dispose() {
     _controller?.dispose();
     super.dispose();
+    _streamSubscription?.cancel();
   }
 
   @override
@@ -349,187 +391,246 @@ class _PageState extends State<MessagePage>
               NestedScrollViewInnerScrollPositionKeyWidget(
                   const Key('Tab0'),
                   Container(
-                    child: FutureBuilder(
-                      future: _chatFuture,
-                      builder: (_,
-                          AsyncSnapshot<List<MessageInstance>>
-                              asyncSnapshot) {
-                        if (asyncSnapshot == null || asyncSnapshot.data == null)
-                          return RequestLoadingWidget();
-                        if (asyncSnapshot.data.length == 0)
-                          return Center(
-                            child: RequestErrorWidget(
-                              null,
-                              msg: "暂无消息",
-                              assets: RequestErrorWidget.ASSETS_NO_COMMENT,
-                            ),
-                          );
-                        List<MessageInstance> data = asyncSnapshot.data;
+                      child: messageList == null
+                          ? RequestLoadingWidget()
+                          : messageList.length == 0
+                              ? Center(
+                                  child: RequestErrorWidget(
+                                    null,
+                                    msg: "暂无消息",
+                                    assets:
+                                        RequestErrorWidget.ASSETS_NO_COMMENT,
+                                  ),
+                                )
+                              : ListView.separated(
+                                  padding: EdgeInsets.zero,
+                                  separatorBuilder: (context, index) => Divider(
+                                        height: 1,
+                                      ),
+                                  itemCount: messageList?.length ?? 0 + 1,
+                                  itemBuilder: (context, index) {
+                                    // 这里可能有得后续看看怎么写比较好 ... 判断是否是自己 ...
+                                    // 这里拿的时候得判断一手 是自己还是别人发的...
+                                    int id;
+                                    String name;
+                                    String avatar;
+                                    bool online;
+                                    if (messageList[index]
+                                            .message
+                                            .fromUser
+                                            .id ==
+                                        int.parse(selfId)) {
+                                      id = messageList[index].message.toUser.id;
+                                      name = messageList[index]
+                                          .message
+                                          .toUser
+                                          .name;
+                                      avatar = messageList[index]
+                                          .message
+                                          .toUser
+                                          .avatar;
+                                      online = messageList[index]
+                                          .message
+                                          .toUser
+                                          .online;
+                                    } else {
+                                      id = messageList[index]
+                                          .message
+                                          .fromUser
+                                          .id;
+                                      name = messageList[index]
+                                          .message
+                                          .fromUser
+                                          .name;
+                                      avatar = messageList[index]
+                                          .message
+                                          .fromUser
+                                          .avatar;
+                                      online = messageList[index]
+                                          .message
+                                          .fromUser
+                                          .online;
+                                    }
 
-//                        List<ChatMessageInstance> data = [
-//                          new ChatMessageInstance(
-//                              toUser: new MessageUser(name: "别急福星再贪一波",avatar: avatarList[1],id: 10),
-//                              createdAt: "10:26:46",
-//                            data: new MessageData(text:"别急别急")
-//                          ),
-//                          new ChatMessageInstance(
-//                              toUser: new MessageUser(name: "别急福星再贪一波",avatar: avatarList[1],id:11),
-//                              createdAt: "10:26:46",
-//                              data: new MessageData(text:"别急别急")
-//                          ),
-//                        ];
-                        return ListView.separated(
-                            padding: EdgeInsets.zero,
-                            separatorBuilder: (context, index) => Divider(
-                                  height: 1,
-                                ),
-                            itemCount: data?.length ?? 0 + 1,
-                            itemBuilder: (context, index) {
-                              return InkWell(
-                                onTap: () async {
-                                  await NavigatorUtil.goPage(
-                                      context,
-                                      (context) => ChatPage(
-                                          data[index].toUser.name,
-                                          data[index].toUser.id));
-                                },
-                                child: Padding(
-                                  padding: const EdgeInsets.all(12.0),
-                                  child: Column(
-                                    children: <Widget>[
-                                      Padding(
-                                          padding: const EdgeInsets.all(12.0),
-                                          child: Row(
-                                            children: <Widget>[
-                                              index % 2 == 0
-                                                  ? ColorFiltered(
-                                                      colorFilter:
-                                                          ColorFilter.mode(
-                                                              Colors.white,
-                                                              BlendMode.color),
-                                                      child: CircleAvatar(
-                                                        backgroundImage:
-                                                            userAvatarProvider(
-                                                                data[index]
-                                                                    .toUser
-                                                                    .avatar),
-                                                        radius: 22,
-                                                      ),
-                                                    )
-                                                  : CircleAvatar(
-                                                      backgroundImage:
-                                                          userAvatarProvider(
-                                                              data[index]
-                                                                  .toUser
-                                                                  .avatar),
-                                                      radius: 22,
-                                                    ),
-                                              SizedBox(
-                                                width: 8,
-                                              ),
-                                              Expanded(
-                                                flex: 3,
-                                                child: Column(
-                                                  crossAxisAlignment:
-                                                      CrossAxisAlignment.start,
+                                    return InkWell(
+                                      onTap: () async {
+                                        await NavigatorUtil.goPage(context,
+                                            (context) => ChatPage(name, id));
+                                        getChatIndex();
+                                      },
+                                      child: Padding(
+                                        padding: const EdgeInsets.all(12.0),
+                                        child: Column(
+                                          children: <Widget>[
+                                            Padding(
+                                                padding:
+                                                    const EdgeInsets.all(12.0),
+                                                child: Row(
                                                   children: <Widget>[
-                                                    Row(
-                                                      children: <Widget>[
-                                                        Text(
-                                                          "${data[index].toUser.name}",
-                                                          style:
-                                                              Theme.of(context)
+                                                    !online
+                                                        ? ColorFiltered(
+                                                            colorFilter:
+                                                                ColorFilter.mode(
+                                                                    Colors
+                                                                        .white,
+                                                                    BlendMode
+                                                                        .color),
+                                                            child: CircleAvatar(
+                                                              backgroundImage:
+                                                                  userAvatarProvider(
+                                                                      avatar),
+                                                              radius: 22,
+                                                            ),
+                                                          )
+                                                        : CircleAvatar(
+                                                            backgroundImage:
+                                                                userAvatarProvider(
+                                                                    avatar),
+                                                            radius: 22,
+                                                          ),
+                                                    SizedBox(
+                                                      width: 8,
+                                                    ),
+                                                    Expanded(
+                                                      flex: 3,
+                                                      child: Column(
+                                                        crossAxisAlignment:
+                                                            CrossAxisAlignment
+                                                                .start,
+                                                        children: <Widget>[
+                                                          Row(
+                                                            children: <Widget>[
+                                                              Expanded(
+                                                                child: Text(
+                                                                  "$name",
+                                                                  style: Theme.of(
+                                                                          context)
+                                                                      .textTheme
+                                                                      .headline3,
+                                                                  maxLines: 1,
+                                                                  overflow:
+                                                                      TextOverflow
+                                                                          .ellipsis,
+                                                                ),
+                                                              ),
+                                                              SizedBox(
+                                                                width: 4.0,
+                                                              ),
+                                                              messageList[index]
+                                                                          .message
+                                                                          .relate !=
+                                                                      'friends'
+                                                                  ? Container(
+                                                                      padding: EdgeInsets.symmetric(
+                                                                          horizontal:
+                                                                              5.0),
+                                                                      decoration: BoxDecoration(
+                                                                          border: Border.all(
+                                                                              color: Color(
+                                                                                  0xffffc400)),
+                                                                          borderRadius: BorderRadius.all(Radius.circular(
+                                                                              8.0))),
+                                                                      child:
+                                                                          Text(
+                                                                        "未关注",
+                                                                        style: Theme.of(context)
+                                                                            .textTheme
+                                                                            .bodyText1
+                                                                            .copyWith(color: Color(0xffffc400)),
+                                                                      ))
+                                                                  : Container()
+                                                            ],
+                                                          ),
+                                                          SizedBox(
+                                                            height: 4,
+                                                          ),
+                                                          if (messageList[index]
+                                                                  .message
+                                                                  .type ==
+                                                              "text")
+                                                            Text(
+                                                              "${messageList[index].message.data.text}",
+                                                              style: Theme.of(
+                                                                      context)
                                                                   .textTheme
-                                                                  .headline3,
-                                                        ),
-                                                        SizedBox(
-                                                          width: 4.0,
-                                                        ),
-                                                        Container(
-                                                            padding: EdgeInsets
-                                                                .symmetric(
-                                                                    horizontal:
-                                                                        5.0),
-                                                            decoration: BoxDecoration(
-                                                                border: Border.all(
-                                                                    color: Color(
-                                                                        0xffffc400)),
-                                                                borderRadius: BorderRadius
-                                                                    .all(Radius
-                                                                        .circular(
-                                                                            8.0))),
-                                                            child: Text(
-                                                              "未关注",
+                                                                  .bodyText1,
+                                                              maxLines: 1,
+                                                              overflow:
+                                                                  TextOverflow
+                                                                      .ellipsis,
+                                                            ),
+                                                          if (messageList[index]
+                                                                  .message
+                                                                  .type ==
+                                                              "forum-forward")
+                                                            Text(
+                                                              "${messageList[index].message.data.subject.content}",
                                                               style: Theme.of(
                                                                       context)
                                                                   .textTheme
-                                                                  .bodyText1
-                                                                  .copyWith(
-                                                                      color: Color(
-                                                                          0xffffc400)),
-                                                            ))
-                                                      ],
-                                                    ),
-                                                    SizedBox(
-                                                      height: 4,
-                                                    ),
-                                                    Text(
-                                                      "游戏邀请",
-                                                      style: Theme.of(context)
-                                                          .textTheme
-                                                          .bodyText1,
-                                                    ),
-                                                  ],
-                                                ),
-                                              ),
-                                              SizedBox(
-                                                width: 8,
-                                              ),
-                                              Expanded(
-                                                flex: 1,
-                                                child: Column(
-                                                  crossAxisAlignment:
-                                                      CrossAxisAlignment.end,
-                                                  mainAxisAlignment:
-                                                      MainAxisAlignment.center,
-                                                  children: <Widget>[
-                                                    Text(
-                                                      "${data[index].createdAt}",
-                                                      style: Theme.of(context)
-                                                          .textTheme
-                                                          .bodyText1,
+                                                                  .bodyText1,
+                                                              maxLines: 1,
+                                                              overflow:
+                                                                  TextOverflow
+                                                                      .ellipsis,
+                                                            ),
+                                                        ],
+                                                      ),
                                                     ),
                                                     SizedBox(
-                                                      height: 3,
+                                                      width: 8,
                                                     ),
-                                                    ClipOval(
-                                                        child: Container(
-                                                      width: 21.0,
-                                                      height: 21.0,
-                                                      color: Color(0xffff5B1D),
-                                                      child: Center(
-                                                        child: Text(
-                                                          '1',
-                                                          style: TextStyle(
-                                                              color:
-                                                                  Colors.white,
-                                                              fontSize: 12.0),
-                                                        ),
+                                                    Expanded(
+                                                      flex: 1,
+                                                      child: Column(
+                                                        crossAxisAlignment:
+                                                            CrossAxisAlignment
+                                                                .end,
+                                                        mainAxisAlignment:
+                                                            MainAxisAlignment
+                                                                .center,
+                                                        children: <Widget>[
+                                                          Text(
+                                                            "${DateFormat.format(DateTime.parse(messageList[index].message.createdAt))}",
+                                                            style: Theme.of(
+                                                                    context)
+                                                                .textTheme
+                                                                .bodyText1,
+                                                          ),
+                                                          SizedBox(
+                                                            height: 3,
+                                                          ),
+                                                          if (messageList[index]
+                                                                  .unReadCount >
+                                                              0)
+                                                            ClipOval(
+                                                                child:
+                                                                    Container(
+                                                              width: 21.0,
+                                                              height: 21.0,
+                                                              color: Color(
+                                                                  0xffff5B1D),
+                                                              child: Center(
+                                                                child: Text(
+                                                                  '${messageList[index].unReadCount}',
+                                                                  style: TextStyle(
+                                                                      color: Colors
+                                                                          .white,
+                                                                      fontSize:
+                                                                          12.0),
+                                                                ),
+                                                              ),
+                                                            ))
+                                                        ],
                                                       ),
-                                                    ))
+                                                    )
                                                   ],
-                                                ),
-                                              )
-                                            ],
-                                          )),
-                                    ],
-                                  ),
-                                ),
-                              );
-                            });
-                      },
-                    ),
-                  )),
+                                                )),
+                                          ],
+                                        ),
+                                      ),
+                                    );
+                                  }))),
               NestedScrollViewInnerScrollPositionKeyWidget(
                   const Key('Tab0'),
                   Container(

+ 1 - 0
lib/pages/social/post_widget.dart

@@ -48,6 +48,7 @@ class _PostWidgetState extends State<PostWidget> with InjectApi {
   }
 
   void go(Post post, bool comment) async {
+    print("${post.toJson()}----------------------------------------");
     var result = await Navigator.push(context, MaterialPageRoute(builder: (context) {
       return PostDetailPage(post, comment, widget.model.list);
     }));

+ 3 - 1
lib/pages/social/share_webview.dart

@@ -10,6 +10,8 @@ import 'package:sport/widgets/menu_share_bottom.dart';
 import 'package:webview_flutter/webview_flutter.dart';
 
 class WebViewSharePage extends StatefulWidget {
+  final String url;
+  WebViewSharePage(this.url);
   @override
   _WebViewSharePageState createState() => _WebViewSharePageState();
 }
@@ -31,7 +33,7 @@ class _WebViewSharePageState extends State<WebViewSharePage> {
               child: Container(
                 height: MediaQuery.of(context).size.height * 1,
                 child: WebView(
-                  initialUrl: 'http://106.52.104.134:4000/',
+                  initialUrl: 'https://www.baidu.com',
                   javascriptMode: JavascriptMode.unrestricted,
                   onWebViewCreated: (WebViewController webViewController) {
                     _controller.complete(webViewController);

+ 46 - 28
lib/provider/message_model.dart

@@ -11,58 +11,76 @@ class MessageModel extends ChangeNotifier with InjectApi {
   int _curId;
   List<MessageInstance> _messages;
 
-  final StreamController<Message> _queryController =
+  final StreamController<List<MessageInstance>> _queryController =
       StreamController.broadcast();
 
-  Stream<Message> get queryStream => _queryController.stream;
+  Stream<List<MessageInstance>> get queryStream => _queryController.stream;
 
   ///事件订阅对象
-  StreamSubscription _dataSubscription;
+  StreamSubscription dataSubscription;
   StreamSubscription periodicSubscription;
 
   init() {
-    // 监听事件
-    _dataSubscription = queryStream.listen((value) {
-      ///do change
-    });
     periodicSubscription =
         Stream.periodic(Duration(seconds: 10)).listen((event) async {
       Message data;
-      List<MessageItem> _items = [];
+
       if (_curId != null) {
         data = (await api.getMessageForPoll(curId: _curId)).data;
       } else {
         data = (await api.getMessageForPoll()).data;
       }
+      // curId 这里一定是接口返回的 本地存 sqlite的 curId 全部都是 0
       _curId = data.curId;
       _messages = data.messages;
-
-      // 插入的时候 status 都是 0;
-      if (_messages.length > 0) {
-        var list = await MessageDB().findLatest();
-        int lastCurId = list[0]["curId"];
-        // 可能会重复插入...;
-        if (lastCurId != _curId) {
-          for (MessageInstance _instance in _messages) {
-            _items.add(new MessageItem(
-                message: json.encode(_instance), curId: _curId, status: 0));
-          }
-          await MessageDB().insertAll(_items);
-          print(
-              "[items]:$_items----------------------------------------------");
-        }
-      }
+//
+//      await MessageDB().deleteMessage(15);
+//      var list = await MessageDB().findAll(); // 清空数字
+//      print("[list]:$list--------------------------");
+//      MessageDB().deleteTable(); //
+      // 这里本来是做了去重操作的 现在不用了 ...
+//      if (_messages.length > 0) {
+//        var list = await MessageDB().findLatest();
+//        print(list);
+//        if (list.length != 0) {
+//          int lastCurId = list[0]["curId"];
+//          print('[lastCurId]:$lastCurId-------------------------');
+//          // 可能会重复插入...;
+//          if (lastCurId != _curId) {
+//            add(data.messages, _curId);
+//          }
+//        } else {
+////          add(data.messages, _curId);
+//        }
+//      }
+      add(data.messages, _curId);
     });
   }
 
-  get(Message event) {
-    _queryController.add(event);
+  add(List<MessageInstance> messages, int curId) async {
+    List<MessageItem> _items = [];
+    // 插进去是 1 未读... 0 已读...
+    for (MessageInstance _instance in _messages) {
+      _items.add(new MessageItem(
+          message: _instance,
+          curId: curId,
+          status: 1,
+          userId: _instance.fromUser.id));
+    }
+    await MessageDB().insertAll(_items);
+    // 这里就是 广播?
+    get(messages);
+//    print("[items]:$_items----------------------------------------------");
+  }
+
+  get(List<MessageInstance> list) {
+    _queryController.add(list);
   }
 
   close() {
     ///关闭
-    _dataSubscription.cancel();
-    _queryController.close();
+    dataSubscription.cancel();
+//    _queryController.close();
   }
 }
 

+ 6 - 0
lib/services/Converter.dart

@@ -73,6 +73,12 @@ class Converter {
         return MessageInstance.fromJson(json) as T;
       case "ChatMessageInstance":
         return ChatMessageInstance.fromJson(json) as T;
+      case "ChatOnlineInfo":
+        return ChatOnlineInfo.fromJson(json) as T;
+      case "MessageUser":
+        return MessageUser.fromJson(json) as T;
+      case "MessagePostUser":
+        return MessagePostUser.fromJson(json) as T;
     }
     return json as T;
   }

+ 5 - 1
lib/services/api/rest_client.dart

@@ -216,7 +216,7 @@ abstract class RestClient {
   Future<RespData<NewFriend>> getNewFriend(@Query("code") String code);
 
   @POST("/share/forwardSubject")
-  Future<RespData<NewFriend>> shareForwardSubject(@Query("subject_id") int subjectId,@Query("user_id") int userId,);
+  Future<RespData<MessageInstance>> shareForwardSubject(@Query("subject_id") int subjectId,@Query("user_id") int userId,);
 
 
   @POST("/message/receive")
@@ -251,6 +251,10 @@ abstract class RestClient {
   })
   Future<RespData<String>> postChatUpload(@Part(name: "media") File file,);
 
+  @POST("/chat/info")
+  Future<RespList<ChatOnlineInfo>> getChatUserInfo(@Query("user_ids")  List<int> userIds,);
+
+
   // ********************** 公告相关 *****************************
   @GET("/inform/list")
   Future<RespPage<Notice>> getInformList({@Query("p") int page});

+ 20 - 1
lib/services/api/rest_client.g.dart

@@ -1215,7 +1215,7 @@ class _RestClient implements RestClient {
             extra: _extra,
             baseUrl: baseUrl),
         data: _data);
-    final value = RespData<NewFriend>.fromJson(_result.data);
+    final value = RespData<MessageInstance>.fromJson(_result.data);
     return value;
   }
 
@@ -1404,6 +1404,25 @@ class _RestClient implements RestClient {
   }
 
   @override
+  getChatUserInfo(userIds) async {
+    ArgumentError.checkNotNull(userIds, 'userIds');
+    const _extra = <String, dynamic>{};
+    final queryParameters = <String, dynamic>{r'user_ids': userIds};
+    final _data = <String, dynamic>{};
+    final Response<Map<String, dynamic>> _result = await _dio.request(
+        '/chat/info',
+        queryParameters: queryParameters,
+        options: RequestOptions(
+            method: 'POST',
+            headers: <String, dynamic>{},
+            extra: _extra,
+            baseUrl: baseUrl),
+        data: _data);
+    final value = RespList<ChatOnlineInfo>.fromJson(_result.data);
+    return value;
+  }
+
+  @override
   getInformList({page}) async {
     const _extra = <String, dynamic>{};
     final queryParameters = <String, dynamic>{r'p': page};

+ 138 - 55
lib/widgets/menu_bar.dart

@@ -1,5 +1,7 @@
+import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
+import 'dart:math';
 
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
@@ -7,6 +9,8 @@ import 'package:flutter/services.dart';
 import 'package:image_picker/image_picker.dart';
 import 'package:multi_image_picker/multi_image_picker.dart';
 import 'package:sport/bean/message.dart';
+import 'package:sport/db/message_db.dart';
+import 'package:sport/pages/social/chat_page.dart';
 import 'package:sport/services/api/inject_api.dart';
 import 'package:sport/widgets/button_primary.dart';
 import 'package:sport/widgets/decoration.dart';
@@ -17,12 +21,18 @@ class MenuBar extends StatefulWidget {
   final Widget messageList;
   final Function scrollToBottom;
   final MenuIdentity menuIdentity;
-
-  MenuBar(this.messageList,
-      {@required this.menuIdentity,
-      this.inputField,
-      this.closeMenuCallBack,
-      this.scrollToBottom});
+  final Function sendCallBack;
+  final GlobalKey globalkey;
+
+  MenuBar(
+    this.messageList, {
+    @required this.menuIdentity,
+    this.inputField,
+    this.closeMenuCallBack,
+    this.scrollToBottom,
+    this.sendCallBack,
+    this.globalkey,
+  });
 
   @override
   State<StatefulWidget> createState() {
@@ -55,6 +65,8 @@ class _MenuBarState extends State<MenuBar>
 
   double height;
 
+  Timer _timer;
+
   void initState() {
     super.initState();
 
@@ -62,22 +74,15 @@ class _MenuBarState extends State<MenuBar>
 
     _controller = TextEditingController();
 
-//    _controller.addListener(() {
-//      print("----------------------------------------------------------");
-//      setState(() {
-//        _textFieldValue = _controller.text;
-//      });
-//      print("linsten");
+//    _focusNode.addListener(() {
+//      if (_focusNode.hasFocus) {
+//        widget.scrollToBottom(keyBoardHeight: keyBoardHeight);
+//        setState(() {
+//          isShowMenuBottomIndex = 1;
+//        });
+//      }
 //    });
-
-    _focusNode.addListener(() {
-      if (_focusNode.hasFocus) {
-        widget.scrollToBottom();
-        setState(() {
-          isShowMenuBottomIndex = 1;
-        });
-      }
-    });
+//    WidgetsBinding.instance.addObserver(this);
   }
 
   // 直接进来就请求了 不要 搞这些骚的....
@@ -95,6 +100,24 @@ class _MenuBarState extends State<MenuBar>
     super.dispose();
     //释放
     _focusNode.dispose();
+    _timer?.cancel();
+  }
+
+  @override
+//  void didChangeMetrics() {
+//    super.didChangeMetrics();
+//    WidgetsBinding.instance.addPostFrameCallback((_) {
+//      // 当前是安卓系统并且在焦点聚焦的情况下
+////      if (Platform.isAndroid && _focusNode.hasFocus) {
+////        widget.scrollToBottom(context, widget.globalkey, false);
+////      }
+//    });
+//  }
+
+  Future add(MessageInstance message) async {
+    // 聊天的时候 是没有返回 curId的 本地存储的时候 就得自己构造一个? 或者是不存?
+    await MessageDB().insert(new MessageItem(
+        message: message, status: 0, userId: message.toUser.id, curId: 0));
   }
 
   Widget extMenuItem(
@@ -131,13 +154,6 @@ class _MenuBarState extends State<MenuBar>
         // 拍完直接发...
         final pickedFile =
             await new ImagePicker().getImage(source: ImageSource.camera);
-//        setState(() {
-//          if (pickedFile != null) {
-//            _image = File(pickedFile.path);
-//          } else {
-//            print('No image selected.');
-//          }
-//        });
         // 如果是聊天 直接发 ...
         if (widget.menuIdentity.menuScene == "chat") {
           // 先上传
@@ -147,7 +163,8 @@ class _MenuBarState extends State<MenuBar>
                   "image",
                   '{ "data":{ "url":"$url" }}'))
               .data;
-          print("$message -------------------------------");
+//          print("$message -------------------------------");
+          add(message);
         }
       } on Exception catch (e) {
         error = e.toString();
@@ -270,19 +287,19 @@ class _MenuBarState extends State<MenuBar>
     }
 
     return Column(
-      mainAxisSize: MainAxisSize.min,
       children: <Widget>[
+//        GestureDetector(
+//          onTap: () {
+//            setState(() {
+//              isShowMenuBottomIndex = 0;
+//            });
+//            _focusNode.unfocus();
+//          },
+//          // 列表在这里...
+//          child: ,
+//        ),
         Expanded(
-          child: GestureDetector(
-            onTap: () {
-              setState(() {
-                isShowMenuBottomIndex = 0;
-              });
-              _focusNode.unfocus();
-            },
-            // 列表在这里...
-            child: widget.messageList,
-          ),
+          child: widget.messageList,
         ),
         ConstrainedBox(
             key: _myKey,
@@ -365,15 +382,46 @@ class _MenuBarState extends State<MenuBar>
                           child: PrimaryButton(
                               content: "发送",
                               callback: () async {
-                                MessageInstance message = (await api.postChatSend(
-                                        widget.menuIdentity.userId,
-                                        "text",
-                                            '{"text":"${_controller.text}"}'))
-                                    .data;
-//                                print(
-//                                    "${json.encode('{ "data":{ "text":"${_controller.text}" }}')}");
+                                if (widget.menuIdentity.menuScene == "chat") {
+//                                  MessageInstance message =
+//                                      (await api.postChatSend(
+//                                              widget.menuIdentity.userId,
+//                                              "text",
+//                                              '{"text":"${_controller.text}"}'))
+//                                          .data;
+
+                                  MessageInstance message = new MessageInstance(
+                                    type: "link",
+                                    data: new MessageData(
+                                        logo: avatarList[
+                                            Random().nextInt(10) % 13],
+                                        url: "www.baidu.com",
+                                        text: "饭粒猫与包子鸭呀...",
+                                        user: new MessagePostUser(
+                                            name: "饭粒猫...")),
+                                    toUser: new MessageUser(
+                                        id: 10,
+                                        avatar:
+                                            "https://wxt.sinaimg.cn/thumb300/a6bdcd78gy1gfna8wnwquj20lr0lradp.jpg?tags=%5B%5D"),
+                                    fromUser: new MessageUser(
+                                        id: 10,
+                                        avatar:
+                                            "https://wxt.sinaimg.cn/thumb300/a6bdcd78gy1gfna8wnwquj20lr0lradp.jpg?tags=%5B%5D"),
+                                  );
+
+//                                  await add(
+//                                      message); // await 是等待的标志 我等待完 在做后面的init 的事?
+//                                  _controller.text = "";
+
+                                  // view 上setstate 渲染的...
+                                  widget.sendCallBack(
+                                      message); // 添加完 在 callBack...
+
+                                  // 调整高度...
+//                                  widget.scrollToBottom(context,
+//                                      widget.globalkey, false); //发完还得 调整一手 ...
+                                }
                                 // 这里可能传不了 callback 所有需要的值都在menu bar里面 只能通过传 不同的 场景 进行 不同的操作
-                                //widget.sendCallBack();
                               }),
                         ),
                       ],
@@ -391,13 +439,48 @@ class _MenuBarState extends State<MenuBar>
 class GetMenuController {
   ScrollController scrollMenuController = new ScrollController();
 
-  void scrollToBottom() {
-    ScrollPosition scrollPosition = scrollMenuController.position;
-    scrollMenuController.animateTo(
-      0.0,
-      curve: Curves.easeOut,
-      duration: const Duration(milliseconds: 200),
-    );
+  // 暂时不用 这个 scrollToBottom ...
+  void scrollToBottom(BuildContext context, GlobalKey key, bool first) {
+    scrollMenuController.addListener(() {
+      print("[offSet:]${scrollMenuController.offset}----------------------");
+    });
+
+    // 页面一屏的高度
+    double pageHeight = MediaQuery.of(context).size.height;
+
+    // 头部的高度...
+    double appBarAndHeight = MediaQuery.of(context).padding.top + 56;
+
+    // 尾部的高度...
+    double bottomHeight = 160;
+
+    // scrollView 的 高度 ...
+    double scrollHeight = key.currentContext.size.height;
+
+    print("[scrollHeight]:$scrollHeight----------------------------");
+
+    double position = scrollMenuController?.position?.maxScrollExtent;
+
+    double currentOffset = scrollMenuController.offset;
+
+    double getScrollTop(bool first) {
+      if (first) {
+        return scrollHeight;
+      } else {
+        // 不需要滚动的状态... 当前 所处的位置 需不需要 加上 bottomHeight...
+        if (currentOffset < bottomHeight) {
+          return position + appBarAndHeight + bottomHeight;
+        } else {
+          return position + appBarAndHeight;
+        }
+      }
+    }
+
+    if (scrollMenuController.hasClients) {
+      Future.delayed(Duration(milliseconds: 30), () {
+        scrollMenuController?.jumpTo(getScrollTop(first));
+      });
+    }
   }
 }