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 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 callbacks = {}; + int reconnectTime = 1500; - addCallback(String userId, Function callback) { - callbacks.putIfAbsent(userId, callback); + reconnect() { + Future.delayed(Duration(milliseconds: reconnectTime *= 2), () { + dispose(); + connect(); + }); + } + + Map callbacks = {}; + + 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 sendMessage(String toId, String content) async { - Map message = createMessage(toId, content, userId: userId); + Map 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 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 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 json) => Contact( + json["id"], + json["userId"], + json["nickName"], + json["imageUrl"], + json["state"], + json["isDelete"]); + + Map toJson() => { + "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 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 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> queryList(userId) { if (db == null) { return Future.value([]); @@ -77,17 +99,54 @@ class HxDatabase { } update(Map message) { - + if (db == null) { + return Future.value(0); + } + debugPrint("Message_insert: $message"); + return db.update("Message", message, where: 'id = ?', whereArgs: [message['id']]); } Future insert(Map message) async { if (db == null) { return Future.value(0); } - debugPrint("Messageinsert: ${message}"); + debugPrint("Message_insert: $message"); return db.insert("Message", message); } + Future insertContact(Map contactMap) async { + if (db == null) { + return Future.value(0); + } + debugPrint("contact_insert: $contactMap"); + return db.insert("Contact", contactMap); + } + + Future> queryContact(List userIds) async { + if (db == null) { + return Future.value([]); + } + 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 queryContactById(String userId) async { + if (db == null) { + return Future.value(); + } + List 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 _migrations = []; addMigrations(List 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 implements OnChatMessage { void dispose() { super.dispose(); OnChatMsgInstance.instance.onChatMessage = null; + + socketClient.removeCallback(socketClient.userId); } @override @@ -62,8 +67,7 @@ class _IMPage extends State 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 implements OnChatMessage { } List userIds = []; - + Map contactMap = {}; + Map 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 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 contacts = await hxDatabase.queryContact(userIds); + if (contacts?.isEmpty ?? false) { + /// TODO: userIds request Api + } else { + List 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 groupBy(Iterable values, S Function(T) key) { + var map = {}; + for (var element in values) { + map[key(element)] ??= element; + } + return map; } // queryMessage() async { @@ -326,7 +377,9 @@ class _IMPage extends State implements OnChatMessage { arguments: { "toId": userIds[position], }, - ); + ).then((value) { + updateLastMessage(userIds[position]); + }); }, child: chatItem(userIds[position]), ); @@ -427,4 +480,5 @@ class _IMPage extends State 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 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 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 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 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 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, ); } }