From 6fdc7e3b41e60b0519442d0ed327deaf206e6b4e Mon Sep 17 00:00:00 2001
From: zsw <dev@lotus-wallet.com>
Date: Thu, 19 Sep 2024 09:50:59 +0800
Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E6=B6=88=E6=81=AF=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/im/SocketClient.dart         | 55 ++++++++++---------
 lib/im/chat_details_page.dart    | 14 +++++
 lib/im/database/contact.dart     | 33 ++++++++++++
 lib/im/database/hx_database.dart | 93 ++++++++++++++++++++++++++------
 lib/im/im_view/im_page.dart      | 62 +++++++++++++++++++--
 lib/im/out/auth.pb.dart          |  1 +
 lib/im/out/message.pbenum.dart   |  1 +
 lib/main.dart                    |  6 +--
 lib/main_page.dart               | 16 ++++--
 9 files changed, 227 insertions(+), 54 deletions(-)
 create mode 100644 lib/im/database/contact.dart

diff --git a/lib/im/SocketClient.dart b/lib/im/SocketClient.dart
index 2237e157..ee2bd1e9 100644
--- a/lib/im/SocketClient.dart
+++ b/lib/im/SocketClient.dart
@@ -1,9 +1,7 @@
-
-
-
 import 'dart:convert';
+import 'dart:core';
+import 'dart:core';
 import 'dart:io';
-
 import 'package:flutter/foundation.dart';
 import 'package:huixiang/im/Proto.dart';
 import 'package:huixiang/im/database/message.dart';
@@ -20,40 +18,48 @@ class SocketClient {
   connect() async {
     shared = await SharedPreferences.getInstance();
 
-    await Socket.connect('192.168.10.200', 49168).then((value) {
+    await Socket.connect('192.168.10.129', 9090).then((value) {
       debugPrint("socket-connect");
       _socket = value;
       _socket.listen((data) {
         print(data);
         print("socket-listen");
         Proto proto = Proto.fromBytes(data);
-        MsgData data1 = MsgData.fromBuffer(proto.body);
-        print('收到来自:${data1.from},消息内容: ${utf8.decode(data1.data)} ');
+        MsgData dataResult = MsgData.fromBuffer(proto.body);
+        print('收到来自:${dataResult.from},消息内容: ${utf8.decode(dataResult.data)} ');
 
-        hxDatabase.insert(createMessage(userId, utf8.decode(data1.data), msgType: data1.type.value, userId: data1.from));
+        Map<String, dynamic> messageMap = createMessage(userId, utf8.decode(dataResult.data), msgType: dataResult.type.value, userId: dataResult.from);
+        Message message = Message.fromJson(messageMap);
+        callbacks[userId]?.call(message); /// user self conversation callback
+        callbacks[dataResult.from]?.call(message); /// user conversation callback
 
-        callbacks.values.forEach((callback) {
-          callback.call(data1);
-        });
+        hxDatabase.insert(messageMap);
 
       }, onError: (Object error, StackTrace stackTrace) {
-        debugPrint("socket-listen-error: $error, stackTrace: ${stackTrace}");
+        debugPrint("socket-listen-error: $error, stackTrace: $stackTrace");
       });
 
       authRequest(shared.getString("token"));
 
     }).catchError((error) {
       debugPrint("socket-connect-error: $error");
-      Future.delayed(const Duration(milliseconds: 3000), () {
-        connect();
-      });
+      reconnect();
     });
   }
 
-  Map<String, Function> callbacks = <String, Function>{};
+  int reconnectTime = 1500;
 
-  addCallback(String userId, Function callback) {
-    callbacks.putIfAbsent(userId, callback);
+  reconnect() {
+    Future.delayed(Duration(milliseconds: reconnectTime *= 2), () {
+      dispose();
+      connect();
+    });
+  }
+
+  Map<String, Function(Message message)> callbacks = <String, Function(Message message)>{};
+
+  addCallback(String userId, callback) {
+    callbacks[userId] = callback;
   }
 
   removeCallback(String userId) {
@@ -61,7 +67,9 @@ class SocketClient {
   }
 
   dispose() {
-    _socket.close();
+    if (_socket != null) {
+      _socket.close();
+    }
   }
 
   authRequest(String token) {
@@ -78,7 +86,7 @@ class SocketClient {
   }
 
   Future<Message> sendMessage(String toId, String content) async {
-    Map message = createMessage(toId, content, userId: userId);
+    Map<String, dynamic> message = createMessage(toId, content, userId: userId);
     int id = await hxDatabase.insert(message).catchError((error) {
       debugPrint("insertMessage: $error");
     });
@@ -92,7 +100,7 @@ class SocketClient {
     }
     message["id"] = id;
     Uint8List data = utf8.encode(content);
-    MsgData msgData = MsgData(to: toId, from: userId, type: MsgType.COMMAND, data: data);
+    MsgData msgData = MsgData(to: toId, from: userId, type: MsgType.TEXT, data: data);
     final proto2 = Proto(5, 1, msgData.writeToBuffer());
     _socket.add(proto2.toBytes());
     debugPrint("sendMessage: ${message["id"]}");
@@ -101,13 +109,12 @@ class SocketClient {
 
   checkSocket() {
     if (_socket == null) {
-      connect();
+      reconnect();
       return false;
     }
     return true;
   }
 
-  get userId => shared.getString("userId");
-
+  String get userId => shared.getString("userId");
 
 }
\ No newline at end of file
diff --git a/lib/im/chat_details_page.dart b/lib/im/chat_details_page.dart
index 049fdeb4..3d2881e6 100644
--- a/lib/im/chat_details_page.dart
+++ b/lib/im/chat_details_page.dart
@@ -7,6 +7,7 @@ import 'package:flutter/rendering.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:huixiang/im/database/message.dart';
+import 'package:huixiang/im/out/message.pb.dart';
 import 'package:huixiang/main.dart';
 import 'package:huixiang/retrofit/retrofit_api.dart';
 import 'package:huixiang/view_widget/my_appbar.dart';
@@ -66,6 +67,16 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
 
     toUserId = widget.arguments["toId"];
     messages = await hxDatabase.queryUList(toUserId);
+
+    socketClient.addCallback(toUserId, (Message message) {
+      messages.add(message);
+
+      refreshState();
+    });
+    refreshState();
+  }
+
+  refreshState() {
     if (mounted) setState(() {});
   }
 
@@ -142,6 +153,9 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
     OnChatMsgInstance.instance.onChatMessage = _tempOnChatMessage;
     WidgetsBinding.instance.removeObserver(this);
     commentFocus.removeListener(_focusNodeListener);
+
+    socketClient.removeCallback(toUserId);
+
   }
 
   void _focusNodeListener() {
diff --git a/lib/im/database/contact.dart b/lib/im/database/contact.dart
new file mode 100644
index 00000000..559e7b2a
--- /dev/null
+++ b/lib/im/database/contact.dart
@@ -0,0 +1,33 @@
+class Contact {
+  int id;
+
+  String userId;
+
+  String nickName;
+
+  String imageUrl;
+
+  int state;
+
+  int isDelete;
+
+  Contact(this.id, this.userId, this.nickName, this.imageUrl, this.state,
+      this.isDelete);
+
+  factory Contact.fromJson(Map<String, dynamic> json) => Contact(
+      json["id"],
+      json["userId"],
+      json["nickName"],
+      json["imageUrl"],
+      json["state"],
+      json["isDelete"]);
+
+  Map<String, dynamic> toJson() => <String, dynamic>{
+        "id": id,
+        "fromId": userId,
+        "toId": nickName,
+        "replyId": imageUrl,
+        "state": state,
+        "isDelete": isDelete == null ? 0 : isDelete
+      };
+}
diff --git a/lib/im/database/hx_database.dart b/lib/im/database/hx_database.dart
index 460e63de..b74b739a 100644
--- a/lib/im/database/hx_database.dart
+++ b/lib/im/database/hx_database.dart
@@ -1,5 +1,6 @@
 
 import 'package:flutter/cupertino.dart';
+import 'package:huixiang/im/database/contact.dart';
 import 'package:huixiang/im/database/message.dart';
 import 'package:huixiang/im/database/migration.dart';
 import 'package:sqflite/sqflite.dart';
@@ -9,27 +10,32 @@ class HxDatabase {
 
   Database db;
 
-  void open() async {
+  void open({String key}) async {
 
     // _migrations.add(Migration(1, 2, (Database database) async {
     //   database.execute('ALTER TABLE `Message` ADD COLUMN `replyId` VARCHAR(20) DEFAULT NULL AFTER `toId`');
     // }));
 
+    String databaseName = 'hx.db';
+    if (key?.isNotEmpty ?? false) {
+      databaseName = 'hx_$key.db';
+    }
     await openDatabase(
-      'hx.db',
-      version: 2,
-      onCreate: (Database db, int version) async {
-        db.execute('CREATE TABLE IF NOT EXISTS `Message` (`id` INTEGER, `fromId` VARCHAR(20), `toId` VARCHAR(20), `replyId` VARCHAR(20), `content` TEXT, `attach` TEXT, `msgType` INTEGER, `time` VARCHAR(20), `state` INTEGER, `isDelete` INTEGER, PRIMARY KEY (`id`))');
-      },
-      onConfigure: (database) async {
-        await database.execute('PRAGMA foreign_keys = ON');
-      },
-      onUpgrade: (database, startVersion, endVersion) async {
-        await runMigrations(database, startVersion, endVersion, _migrations);
-      },
-      onOpen: (Database db) {
-        this.db = db;
-      }
+        databaseName,
+        version: 2,
+        onCreate: (Database db, int version) async {
+          db.execute('CREATE TABLE IF NOT EXISTS `Message` (`id` INTEGER, `fromId` VARCHAR(20), `toId` VARCHAR(20), `replyId` VARCHAR(20), `content` TEXT, `attach` TEXT, `msgType` INTEGER, `time` VARCHAR(20), `state` INTEGER, `isDelete` INTEGER, PRIMARY KEY (`id`))');
+          db.execute('CREATE TABLE IF NOT EXISTS `Contact` (`id` INTEGER, `userId` VARCHAR(20), `nickName` VARCHAR(20), `imageUrl` VARCHAR(200), `state` INTEGER, `isDelete` INTEGER, PRIMARY KEY (`id`))');
+        },
+        onConfigure: (database) async {
+          await database.execute('PRAGMA foreign_keys = ON');
+        },
+        onUpgrade: (database, startVersion, endVersion) async {
+          await runMigrations(database, startVersion, endVersion, _migrations);
+        },
+        onOpen: (Database db) {
+          this.db = db;
+        }
     );
   }
 
@@ -37,6 +43,22 @@ class HxDatabase {
     db.close();
   }
 
+  Future<Message> lastMessage(String userId) async {
+    if (db == null) {
+      return Future.value();
+    }
+    String sql = 'SELECT * FROM Message WHERE toId = ? OR fromId = ? ORDER BY time DESC LIMIT 1';
+    List<Message> messages = await db.rawQuery(sql, [userId, userId]).then((value) {
+      return value.map((e) {
+        debugPrint("Message: ${e}");
+        return Message.fromJson(e);
+      }).toList();
+    }, onError: (error) {
+      debugPrint("Messageerror: $error");
+    });
+    return (messages?.isNotEmpty ?? false) ? messages.first : null;
+  }
+
   Future<List<Message>> queryList(userId) {
     if (db == null) {
       return Future.value(<Message>[]);
@@ -77,17 +99,54 @@ class HxDatabase {
   }
 
   update(Map<dynamic, dynamic> message) {
-
+    if (db == null) {
+      return Future.value(0);
+    }
+    debugPrint("Message_insert: $message");
+    return db.update("Message", message, where: 'id = ?', whereArgs: [message['id']]);
   }
 
   Future<int> insert(Map message) async {
     if (db == null) {
       return Future.value(0);
     }
-    debugPrint("Messageinsert: ${message}");
+    debugPrint("Message_insert: $message");
     return db.insert("Message", message);
   }
 
+  Future<int> insertContact(Map contactMap) async {
+    if (db == null) {
+      return Future.value(0);
+    }
+    debugPrint("contact_insert: $contactMap");
+    return db.insert("Contact", contactMap);
+  }
+
+  Future<List<Contact>> queryContact(List<String> userIds) async {
+    if (db == null) {
+      return Future.value(<Contact>[]);
+    }
+    String sql = 'SELECT * FROM Contact WHERE userId IN (?) AND state = 0 AND isDelete = 0';
+    return db.rawQuery(sql, userIds).then((value) {
+      return value.map((e) => Contact.fromJson(e)).toList();
+    }, onError: (error) {
+      debugPrint("Contact_error: $error");
+    });
+  }
+
+  Future<Contact> queryContactById(String userId) async {
+    if (db == null) {
+      return Future.value();
+    }
+    List<Contact> contact = await db.query("Contact", distinct: true, where: "userId = ?", whereArgs: [userId])
+        .then((value) {
+      return value.map((e) => Contact.fromJson(e)).toList();
+    }, onError: (error) {
+      debugPrint("Contact_error: $error");
+    });
+    return (contact?.isNotEmpty ?? false) ? contact.first : null;
+  }
+
   final List<Migration> _migrations = [];
 
   addMigrations(List<Migration> migrations) {
diff --git a/lib/im/im_view/im_page.dart b/lib/im/im_view/im_page.dart
index 12ccec6e..147a35bb 100644
--- a/lib/im/im_view/im_page.dart
+++ b/lib/im/im_view/im_page.dart
@@ -1,10 +1,13 @@
 import 'dart:async';
+import 'dart:collection';
 
 import 'package:dio/dio.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_easyloading/flutter_easyloading.dart';
 import 'package:huixiang/generated/l10n.dart';
+import 'package:huixiang/im/database/contact.dart';
 import 'package:huixiang/im/database/message.dart';
+import 'package:huixiang/im/out/message.pb.dart';
 import 'package:huixiang/main.dart';
 import 'package:huixiang/retrofit/data/base_data.dart';
 import 'package:huixiang/retrofit/data/msg_stats.dart';
@@ -53,6 +56,8 @@ class _IMPage extends State<IMPage> implements OnChatMessage {
   void dispose() {
     super.dispose();
     OnChatMsgInstance.instance.onChatMessage = null;
+
+    socketClient.removeCallback(socketClient.userId);
   }
 
   @override
@@ -62,8 +67,7 @@ class _IMPage extends State<IMPage> implements OnChatMessage {
 
     loadMessageList();
     SharedPreferences.getInstance().then((value) {
-      apiService =
-          ApiService(Dio(), token: value.getString("token"), context: context);
+      apiService = ApiService(Dio(), token: value.getString("token"), context: context);
       queryMsgStats();
     });
   }
@@ -75,12 +79,33 @@ class _IMPage extends State<IMPage> implements OnChatMessage {
   }
 
   List<String> userIds = [];
-
+  Map<String, Contact> contactMap = {};
+  Map<String, Message> lastMessageMap = {};
   Stream streamSubscription ;
 
   loadMessageList() async {
     SharedPreferences shared = await SharedPreferences.getInstance();
     String userId = shared.getString("userId");
+
+    debugPrint("messages: loadMessageList");
+    socketClient.addCallback(userId, (Message message) {
+      if (userIds.contains(message.fromId)) {
+        userIds.remove(message.fromId);
+      }
+      userIds.insert(0, message.fromId);
+
+      lastMessageMap[message.fromId] = message;
+
+      debugPrint("messages_records : ${message.toJson()}");
+      if (contactMap[message.fromId] == null) {
+        /// TODO: message.fromId request Api and setState
+      }
+      if (mounted) {
+        setState(() {});
+      }
+    });
+
+    debugPrint("messages: queryList");
     messages = await hxDatabase.queryList(userId);
     messages.forEach((element) {
       debugPrint("messages: ${element.toJson()}");
@@ -89,10 +114,36 @@ class _IMPage extends State<IMPage> implements OnChatMessage {
         .map((e) => e.toId != userId ? e.toId : e.fromId)
         .toSet().where((element) => element != userId)
         .toList();
+
+    lastMessageMap = groupBy(messages, (p0) => p0.toId != userId ? p0.toId : p0.fromId);
+
+    List<Contact> contacts = await hxDatabase.queryContact(userIds);
+    if (contacts?.isEmpty ?? false) {
+      /// TODO: userIds request Api
+    } else {
+      List<String> queryUserIds = userIds.where((u) => contacts.where((c) => c.userId == u).isEmpty).toList();
+      /// TODO: queryUserIds request Api
+    }
+    contactMap = groupBy(contacts, (p0) => p0.userId);
+
     if (mounted) {
       setState(() {});
     }
+  }
+
+  void updateLastMessage(String userId) async {
+    Message message = await hxDatabase.lastMessage(userId);
+    if (message != null) {
+      lastMessageMap[userId] = message;
+    }
+  }
 
+  Map<S, T> groupBy<S, T>(Iterable<T> values, S Function(T) key) {
+    var map = <S, T>{};
+    for (var element in values) {
+      map[key(element)] ??= element;
+    }
+    return map;
   }
 
   // queryMessage() async {
@@ -326,7 +377,9 @@ class _IMPage extends State<IMPage> implements OnChatMessage {
                 arguments: {
                     "toId": userIds[position],
                 },
-              );
+              ).then((value) {
+                updateLastMessage(userIds[position]);
+              });
             },
             child: chatItem(userIds[position]),
           );
@@ -427,4 +480,5 @@ class _IMPage extends State<IMPage> implements OnChatMessage {
       ),
     );
   }
+
 }
diff --git a/lib/im/out/auth.pb.dart b/lib/im/out/auth.pb.dart
index 8296a40c..abdf04cb 100644
--- a/lib/im/out/auth.pb.dart
+++ b/lib/im/out/auth.pb.dart
@@ -14,6 +14,7 @@ import 'dart:core' as $core;
 import 'package:protobuf/protobuf.dart' as $pb;
 
 class AuthReq extends $pb.GeneratedMessage {
+  
   factory AuthReq({
     $core.String? uid,
     $core.String? token,
diff --git a/lib/im/out/message.pbenum.dart b/lib/im/out/message.pbenum.dart
index df67c4a9..2ce14e1a 100644
--- a/lib/im/out/message.pbenum.dart
+++ b/lib/im/out/message.pbenum.dart
@@ -14,6 +14,7 @@ import 'dart:core' as $core;
 import 'package:protobuf/protobuf.dart' as $pb;
 
 class MsgType extends $pb.ProtobufEnum {
+
   static const MsgType COMMAND = MsgType._(0, _omitEnumNames ? '' : 'COMMAND');
   static const MsgType TEXT = MsgType._(1, _omitEnumNames ? '' : 'TEXT');
   static const MsgType IMAGE = MsgType._(2, _omitEnumNames ? '' : 'IMAGE');
diff --git a/lib/main.dart b/lib/main.dart
index 6fa68f1c..3eb87255 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -208,8 +208,6 @@ void main() async {
   // initSdk();
   bool isFirst = sharedPreferences.getBool("isFirst");
 
-  initDatabase();
-
   runApp(MyApp(locale, isFirst));
   // FlutterBugly.postCatchedException((){
   // });
@@ -220,9 +218,9 @@ void main() async {
 
 HxDatabase hxDatabase;
 
-initDatabase() async {
+initDatabase(String userId) async {
   hxDatabase = HxDatabase();
-  await hxDatabase.open();
+  hxDatabase.open(key: userId);
 }
 
 final SocketClient socketClient = new SocketClient();
diff --git a/lib/main_page.dart b/lib/main_page.dart
index ba5f1400..f2349dcf 100644
--- a/lib/main_page.dart
+++ b/lib/main_page.dart
@@ -57,6 +57,7 @@ class _MainPage extends State<MainPage> with WidgetsBindingObserver {
   final GlobalKey homePageKey = GlobalKey();
   final GlobalKey minePageKey = GlobalKey();
   final GlobalKey unionPageKey = GlobalKey();
+
   // final GlobalKey vipPageKey = GlobalKey();
   final GlobalKey imPageKey = GlobalKey();
 
@@ -83,7 +84,7 @@ class _MainPage extends State<MainPage> with WidgetsBindingObserver {
         if (DateTime.now().millisecondsSinceEpoch - lastTime > 420000)
           //处于后台**分钟后刷新应用
           // Navigator.of(context).popAndPushNamed('/router/start_page');
-          setState((){});
+          setState(() {});
         break;
       case AppLifecycleState.paused: // 界面不可见,后台
         lastTime = DateTime.now().millisecondsSinceEpoch;
@@ -102,7 +103,11 @@ class _MainPage extends State<MainPage> with WidgetsBindingObserver {
   void initState() {
     super.initState();
 
-    connectSocket();
+    SharedPreferences.getInstance().then((value) {
+      String userId = value.getString("userId");
+      initDatabase(userId);
+      connectSocket();
+    });
 
     pageController = PageController(
         initialPage:
@@ -125,7 +130,8 @@ class _MainPage extends State<MainPage> with WidgetsBindingObserver {
       ..dismissOnTap = false;
 
     initSdk();
-    UmengCommonSdk.initCommon('6491509087568a379b5a1345', '6491509087568a379b5a1345', 'Umeng');
+    UmengCommonSdk.initCommon(
+        '6491509087568a379b5a1345', '6491509087568a379b5a1345', 'Umeng');
     UmengCommonSdk.setPageCollectionModeManual();
     initPlatformState();
 
@@ -431,8 +437,8 @@ class _MainPage extends State<MainPage> with WidgetsBindingObserver {
       xgFlutterPlugin.setAccount(mobile, AccountType.PHONE_NUMBER);
       // xgFlutterPlugin.unbindWithIdentifier(identify: mobile, bindType: XGBindType.account)
       xgFlutterPlugin.bindWithIdentifier(
-          identify: mobile,
-          bindType: XGBindType.account,
+        identify: mobile,
+        bindType: XGBindType.account,
       );
     }
   }