Browse Source

发消息修改

wr_202303
zsw 4 months ago
parent
commit
6fdc7e3b41
  1. 55
      lib/im/SocketClient.dart
  2. 14
      lib/im/chat_details_page.dart
  3. 33
      lib/im/database/contact.dart
  4. 93
      lib/im/database/hx_database.dart
  5. 62
      lib/im/im_view/im_page.dart
  6. 1
      lib/im/out/auth.pb.dart
  7. 1
      lib/im/out/message.pbenum.dart
  8. 6
      lib/main.dart
  9. 16
      lib/main_page.dart

55
lib/im/SocketClient.dart

@ -1,9 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:core';
import 'dart:core';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:huixiang/im/Proto.dart'; import 'package:huixiang/im/Proto.dart';
import 'package:huixiang/im/database/message.dart'; import 'package:huixiang/im/database/message.dart';
@ -20,40 +18,48 @@ class SocketClient {
connect() async { connect() async {
shared = await SharedPreferences.getInstance(); 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"); debugPrint("socket-connect");
_socket = value; _socket = value;
_socket.listen((data) { _socket.listen((data) {
print(data); print(data);
print("socket-listen"); print("socket-listen");
Proto proto = Proto.fromBytes(data); Proto proto = Proto.fromBytes(data);
MsgData data1 = MsgData.fromBuffer(proto.body); MsgData dataResult = MsgData.fromBuffer(proto.body);
print('收到来自:${data1.from},消息内容: ${utf8.decode(data1.data)} '); 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) { hxDatabase.insert(messageMap);
callback.call(data1);
});
}, onError: (Object error, StackTrace stackTrace) { }, onError: (Object error, StackTrace stackTrace) {
debugPrint("socket-listen-error: $error, stackTrace: ${stackTrace}"); debugPrint("socket-listen-error: $error, stackTrace: $stackTrace");
}); });
authRequest(shared.getString("token")); authRequest(shared.getString("token"));
}).catchError((error) { }).catchError((error) {
debugPrint("socket-connect-error: $error"); debugPrint("socket-connect-error: $error");
Future.delayed(const Duration(milliseconds: 3000), () { reconnect();
connect();
});
}); });
} }
Map<String, Function> callbacks = <String, Function>{}; int reconnectTime = 1500;
addCallback(String userId, Function callback) { reconnect() {
callbacks.putIfAbsent(userId, callback); 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) { removeCallback(String userId) {
@ -61,7 +67,9 @@ class SocketClient {
} }
dispose() { dispose() {
_socket.close(); if (_socket != null) {
_socket.close();
}
} }
authRequest(String token) { authRequest(String token) {
@ -78,7 +86,7 @@ class SocketClient {
} }
Future<Message> sendMessage(String toId, String content) async { 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) { int id = await hxDatabase.insert(message).catchError((error) {
debugPrint("insertMessage: $error"); debugPrint("insertMessage: $error");
}); });
@ -92,7 +100,7 @@ class SocketClient {
} }
message["id"] = id; message["id"] = id;
Uint8List data = utf8.encode(content); 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()); final proto2 = Proto(5, 1, msgData.writeToBuffer());
_socket.add(proto2.toBytes()); _socket.add(proto2.toBytes());
debugPrint("sendMessage: ${message["id"]}"); debugPrint("sendMessage: ${message["id"]}");
@ -101,13 +109,12 @@ class SocketClient {
checkSocket() { checkSocket() {
if (_socket == null) { if (_socket == null) {
connect(); reconnect();
return false; return false;
} }
return true; return true;
} }
get userId => shared.getString("userId"); String get userId => shared.getString("userId");
} }

14
lib/im/chat_details_page.dart

@ -7,6 +7,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:huixiang/im/database/message.dart'; import 'package:huixiang/im/database/message.dart';
import 'package:huixiang/im/out/message.pb.dart';
import 'package:huixiang/main.dart'; import 'package:huixiang/main.dart';
import 'package:huixiang/retrofit/retrofit_api.dart'; import 'package:huixiang/retrofit/retrofit_api.dart';
import 'package:huixiang/view_widget/my_appbar.dart'; import 'package:huixiang/view_widget/my_appbar.dart';
@ -66,6 +67,16 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
toUserId = widget.arguments["toId"]; toUserId = widget.arguments["toId"];
messages = await hxDatabase.queryUList(toUserId); messages = await hxDatabase.queryUList(toUserId);
socketClient.addCallback(toUserId, (Message message) {
messages.add(message);
refreshState();
});
refreshState();
}
refreshState() {
if (mounted) setState(() {}); if (mounted) setState(() {});
} }
@ -142,6 +153,9 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
OnChatMsgInstance.instance.onChatMessage = _tempOnChatMessage; OnChatMsgInstance.instance.onChatMessage = _tempOnChatMessage;
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
commentFocus.removeListener(_focusNodeListener); commentFocus.removeListener(_focusNodeListener);
socketClient.removeCallback(toUserId);
} }
void _focusNodeListener() { void _focusNodeListener() {

33
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
};
}

93
lib/im/database/hx_database.dart

@ -1,5 +1,6 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:huixiang/im/database/contact.dart';
import 'package:huixiang/im/database/message.dart'; import 'package:huixiang/im/database/message.dart';
import 'package:huixiang/im/database/migration.dart'; import 'package:huixiang/im/database/migration.dart';
import 'package:sqflite/sqflite.dart'; import 'package:sqflite/sqflite.dart';
@ -9,27 +10,32 @@ class HxDatabase {
Database db; Database db;
void open() async { void open({String key}) async {
// _migrations.add(Migration(1, 2, (Database database) async { // _migrations.add(Migration(1, 2, (Database database) async {
// database.execute('ALTER TABLE `Message` ADD COLUMN `replyId` VARCHAR(20) DEFAULT NULL AFTER `toId`'); // 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( await openDatabase(
'hx.db', databaseName,
version: 2, version: 2,
onCreate: (Database db, int version) async { 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 `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'); onConfigure: (database) async {
}, await database.execute('PRAGMA foreign_keys = ON');
onUpgrade: (database, startVersion, endVersion) async { },
await runMigrations(database, startVersion, endVersion, _migrations); onUpgrade: (database, startVersion, endVersion) async {
}, await runMigrations(database, startVersion, endVersion, _migrations);
onOpen: (Database db) { },
this.db = db; onOpen: (Database db) {
} this.db = db;
}
); );
} }
@ -37,6 +43,22 @@ class HxDatabase {
db.close(); 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) { Future<List<Message>> queryList(userId) {
if (db == null) { if (db == null) {
return Future.value(<Message>[]); return Future.value(<Message>[]);
@ -77,17 +99,54 @@ class HxDatabase {
} }
update(Map<dynamic, dynamic> message) { 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 { Future<int> insert(Map message) async {
if (db == null) { if (db == null) {
return Future.value(0); return Future.value(0);
} }
debugPrint("Messageinsert: ${message}"); debugPrint("Message_insert: $message");
return db.insert("Message", 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 = []; final List<Migration> _migrations = [];
addMigrations(List<Migration> migrations) { addMigrations(List<Migration> migrations) {

62
lib/im/im_view/im_page.dart

@ -1,10 +1,13 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:huixiang/generated/l10n.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/database/message.dart';
import 'package:huixiang/im/out/message.pb.dart';
import 'package:huixiang/main.dart'; import 'package:huixiang/main.dart';
import 'package:huixiang/retrofit/data/base_data.dart'; import 'package:huixiang/retrofit/data/base_data.dart';
import 'package:huixiang/retrofit/data/msg_stats.dart'; import 'package:huixiang/retrofit/data/msg_stats.dart';
@ -53,6 +56,8 @@ class _IMPage extends State<IMPage> implements OnChatMessage {
void dispose() { void dispose() {
super.dispose(); super.dispose();
OnChatMsgInstance.instance.onChatMessage = null; OnChatMsgInstance.instance.onChatMessage = null;
socketClient.removeCallback(socketClient.userId);
} }
@override @override
@ -62,8 +67,7 @@ class _IMPage extends State<IMPage> implements OnChatMessage {
loadMessageList(); loadMessageList();
SharedPreferences.getInstance().then((value) { SharedPreferences.getInstance().then((value) {
apiService = apiService = ApiService(Dio(), token: value.getString("token"), context: context);
ApiService(Dio(), token: value.getString("token"), context: context);
queryMsgStats(); queryMsgStats();
}); });
} }
@ -75,12 +79,33 @@ class _IMPage extends State<IMPage> implements OnChatMessage {
} }
List<String> userIds = []; List<String> userIds = [];
Map<String, Contact> contactMap = {};
Map<String, Message> lastMessageMap = {};
Stream streamSubscription ; Stream streamSubscription ;
loadMessageList() async { loadMessageList() async {
SharedPreferences shared = await SharedPreferences.getInstance(); SharedPreferences shared = await SharedPreferences.getInstance();
String userId = shared.getString("userId"); 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 = await hxDatabase.queryList(userId);
messages.forEach((element) { messages.forEach((element) {
debugPrint("messages: ${element.toJson()}"); debugPrint("messages: ${element.toJson()}");
@ -89,10 +114,36 @@ class _IMPage extends State<IMPage> implements OnChatMessage {
.map((e) => e.toId != userId ? e.toId : e.fromId) .map((e) => e.toId != userId ? e.toId : e.fromId)
.toSet().where((element) => element != userId) .toSet().where((element) => element != userId)
.toList(); .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) { if (mounted) {
setState(() {}); 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 { // queryMessage() async {
@ -326,7 +377,9 @@ class _IMPage extends State<IMPage> implements OnChatMessage {
arguments: { arguments: {
"toId": userIds[position], "toId": userIds[position],
}, },
); ).then((value) {
updateLastMessage(userIds[position]);
});
}, },
child: chatItem(userIds[position]), child: chatItem(userIds[position]),
); );
@ -427,4 +480,5 @@ class _IMPage extends State<IMPage> implements OnChatMessage {
), ),
); );
} }
} }

1
lib/im/out/auth.pb.dart

@ -14,6 +14,7 @@ import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb; import 'package:protobuf/protobuf.dart' as $pb;
class AuthReq extends $pb.GeneratedMessage { class AuthReq extends $pb.GeneratedMessage {
factory AuthReq({ factory AuthReq({
$core.String? uid, $core.String? uid,
$core.String? token, $core.String? token,

1
lib/im/out/message.pbenum.dart

@ -14,6 +14,7 @@ import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb; import 'package:protobuf/protobuf.dart' as $pb;
class MsgType extends $pb.ProtobufEnum { class MsgType extends $pb.ProtobufEnum {
static const MsgType COMMAND = MsgType._(0, _omitEnumNames ? '' : 'COMMAND'); static const MsgType COMMAND = MsgType._(0, _omitEnumNames ? '' : 'COMMAND');
static const MsgType TEXT = MsgType._(1, _omitEnumNames ? '' : 'TEXT'); static const MsgType TEXT = MsgType._(1, _omitEnumNames ? '' : 'TEXT');
static const MsgType IMAGE = MsgType._(2, _omitEnumNames ? '' : 'IMAGE'); static const MsgType IMAGE = MsgType._(2, _omitEnumNames ? '' : 'IMAGE');

6
lib/main.dart

@ -208,8 +208,6 @@ void main() async {
// initSdk(); // initSdk();
bool isFirst = sharedPreferences.getBool("isFirst"); bool isFirst = sharedPreferences.getBool("isFirst");
initDatabase();
runApp(MyApp(locale, isFirst)); runApp(MyApp(locale, isFirst));
// FlutterBugly.postCatchedException((){ // FlutterBugly.postCatchedException((){
// }); // });
@ -220,9 +218,9 @@ void main() async {
HxDatabase hxDatabase; HxDatabase hxDatabase;
initDatabase() async { initDatabase(String userId) async {
hxDatabase = HxDatabase(); hxDatabase = HxDatabase();
await hxDatabase.open(); hxDatabase.open(key: userId);
} }
final SocketClient socketClient = new SocketClient(); final SocketClient socketClient = new SocketClient();

16
lib/main_page.dart

@ -57,6 +57,7 @@ class _MainPage extends State<MainPage> with WidgetsBindingObserver {
final GlobalKey homePageKey = GlobalKey(); final GlobalKey homePageKey = GlobalKey();
final GlobalKey minePageKey = GlobalKey(); final GlobalKey minePageKey = GlobalKey();
final GlobalKey unionPageKey = GlobalKey(); final GlobalKey unionPageKey = GlobalKey();
// final GlobalKey vipPageKey = GlobalKey(); // final GlobalKey vipPageKey = GlobalKey();
final GlobalKey imPageKey = GlobalKey(); final GlobalKey imPageKey = GlobalKey();
@ -83,7 +84,7 @@ class _MainPage extends State<MainPage> with WidgetsBindingObserver {
if (DateTime.now().millisecondsSinceEpoch - lastTime > 420000) if (DateTime.now().millisecondsSinceEpoch - lastTime > 420000)
//** //**
// Navigator.of(context).popAndPushNamed('/router/start_page'); // Navigator.of(context).popAndPushNamed('/router/start_page');
setState((){}); setState(() {});
break; break;
case AppLifecycleState.paused: // case AppLifecycleState.paused: //
lastTime = DateTime.now().millisecondsSinceEpoch; lastTime = DateTime.now().millisecondsSinceEpoch;
@ -102,7 +103,11 @@ class _MainPage extends State<MainPage> with WidgetsBindingObserver {
void initState() { void initState() {
super.initState(); super.initState();
connectSocket(); SharedPreferences.getInstance().then((value) {
String userId = value.getString("userId");
initDatabase(userId);
connectSocket();
});
pageController = PageController( pageController = PageController(
initialPage: initialPage:
@ -125,7 +130,8 @@ class _MainPage extends State<MainPage> with WidgetsBindingObserver {
..dismissOnTap = false; ..dismissOnTap = false;
initSdk(); initSdk();
UmengCommonSdk.initCommon('6491509087568a379b5a1345', '6491509087568a379b5a1345', 'Umeng'); UmengCommonSdk.initCommon(
'6491509087568a379b5a1345', '6491509087568a379b5a1345', 'Umeng');
UmengCommonSdk.setPageCollectionModeManual(); UmengCommonSdk.setPageCollectionModeManual();
initPlatformState(); initPlatformState();
@ -431,8 +437,8 @@ class _MainPage extends State<MainPage> with WidgetsBindingObserver {
xgFlutterPlugin.setAccount(mobile, AccountType.PHONE_NUMBER); xgFlutterPlugin.setAccount(mobile, AccountType.PHONE_NUMBER);
// xgFlutterPlugin.unbindWithIdentifier(identify: mobile, bindType: XGBindType.account) // xgFlutterPlugin.unbindWithIdentifier(identify: mobile, bindType: XGBindType.account)
xgFlutterPlugin.bindWithIdentifier( xgFlutterPlugin.bindWithIdentifier(
identify: mobile, identify: mobile,
bindType: XGBindType.account, bindType: XGBindType.account,
); );
} }
} }

Loading…
Cancel
Save