You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

242 lines
7.4 KiB

4 months ago
import 'dart:async';
import 'dart:convert';
4 months ago
import 'dart:core';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:huixiang/constant.dart';
import 'package:huixiang/im/Proto.dart';
import 'package:huixiang/im/database/message.dart';
import 'package:huixiang/im/out/auth.pb.dart';
import 'package:huixiang/im/out/message.pb.dart';
import 'package:huixiang/main.dart';
4 months ago
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SocketClient {
Socket _socket;
SharedPreferences _shared;
Timer timer;
bool get heartbeatActive => timer != null && timer.isActive;
connect() async {
_shared = await SharedPreferences.getInstance();
if (_socket != null) {
reconnect();
return;
}
showDebugToast("socket-connect .... ");
await Socket.connect(socketHost, socketPort).then((value) {
debugPrint("socket-connect-$socketHost");
_socket = value;
_socket.listen((data) {
print(data);
print("socket-listen");
Proto proto = Proto.fromBytes(data);
4 months ago
MsgData dataResult = MsgData.fromBuffer(proto.body);
print('收到来自:${dataResult.from},消息内容: ${utf8.decode(dataResult.data)} ');
4 months ago
receiveInsertMessage(dataResult).then((messageMap) {
if (callbacks[dataResult.from] != null) {
4 months ago
messageMap["state"] = 1;
}
4 months ago
hxDatabase.insert(messageMap).then((value) {
messageMap["id"] = value;
Message message = Message.fromJson(messageMap);
if (callbacks[dataResult.from] != null) {
callbacks[dataResult.from].call(message); /// user conversation callback
}
callbacks[userId]?.call(message); /// user self conversation list callback
});
});
}, onError: (Object error, StackTrace stackTrace) {
4 months ago
debugPrint("socket-listen-error: $error, stackTrace: $stackTrace");
showDebugToast("socket-listen-error: $error, stackTrace: $stackTrace");
reconnect();
4 months ago
}, onDone: () {
debugPrint("socket-listen-down: down");
});
authRequest(_shared.getString("token"));
4 months ago
heartbeat();
}).catchError((error) {
debugPrint("socket-connect-error: $error");
showDebugToast("socket-connect-error: $error");
4 months ago
_socket = null;
4 months ago
reconnect();
});
}
4 months ago
Future<Map<String, dynamic>> receiveInsertMessage(MsgData dataResult) async {
Uint8List dataBytes = dataResult.data;
String content = utf8.decode(dataBytes);
String attach = "";
4 months ago
MsgType type = MsgType.values[dataResult.type.value];
if (type == MsgType.IMAGE || type == MsgType.VIDEO || type == MsgType.AUDIO) {
String filePath = await qiniu.downloadFile(content);
Map<String, dynamic> result = await ImageGallerySaver.saveFile(filePath).catchError((error){});
4 months ago
bool isSuccess = result["isSuccess"] != null && result["isSuccess"];
attach = filePath;
4 months ago
}
Map<String, dynamic> messageMap = createMessage(userId, content, attach: attach, msgType: type.value, fromId: dataResult.from);
return messageMap;
}
showDebugToast(text) {
if (kDebugMode) {
SmartDialog.showToast(text, alignment: Alignment.center);
}
}
Proto heartbeatData() {
DateTime dateTime = DateTime.now();
int millisecondsTime = dateTime.millisecondsSinceEpoch;
Uint8List data = utf8.encode(jsonEncode({"heartbeat": millisecondsTime}));
MsgData msgData = MsgData(from: userId, type: MsgType.TEXT, data: data);
final proto2 = Proto(3, 1, msgData.writeToBuffer());
debugPrint("heartbeat: ${dateTime.toString()}");
return proto2;
}
4 months ago
heartbeat() {
cancelTimer();
timer = Timer.periodic(const Duration(milliseconds: 30000), (timer) {
if(!checkSocket()) {
timer.cancel();
return;
}
sendHeartBeatAndCheckSocket();
4 months ago
});
}
/// send heartBeat and check socket is connected
/// send error: reconnect,
/// else check Timer.periodic isActive ,
/// if not active: Timer.periodic send heartBeat
sendHeartBeatAndCheckSocket() {
final proto2 = heartbeatData();
try {
_socket.add(proto2.toBytes());
if (!socketClient.heartbeatActive) {
heartbeat();
debugPrint("socket-periodic-send-heart-beat");
}
} catch (e) {
debugPrint("socket-send-error: ${e.toString()}");
showDebugToast("socket-send-error: ${e.toString()}");
reconnect();
}
}
cancelTimer() {
if (timer != null && timer.isActive) {
timer.cancel();
timer = null;
}
}
4 months ago
reconnect() {
dispose();
connect();
4 months ago
}
Map<String, Function(Message message)> callbacks = <String, Function(Message message)>{};
addCallback(String userId, callback) {
callbacks[userId] = callback;
}
4 months ago
removeCallback(String userId) {
callbacks.remove(userId);
}
dispose() {
4 months ago
if (_socket != null) {
_socket.close();
_socket = null;
4 months ago
}
}
authRequest(String token) {
if (!checkSocket()) {
return;
}
4 months ago
debugPrint("socket-authRequest: request");
final authReq = AuthReq()
4 months ago
..uid = userId
..token = token;
final authReqBytes = authReq.writeToBuffer();
final proto = Proto(1, 1, authReqBytes); // 假设 operation 和 seqId 为 1
final protoBytes = proto.toBytes();
4 months ago
try {
_socket.add(protoBytes);
} catch (e) {
debugPrint("socket-authRequest: $e");
Future.delayed(const Duration(milliseconds: 1000), () {
authRequest(token);
});
}
}
4 months ago
Future<Message> sendMessage(String toId, String content, {String attach, int msgType = 1, replyId}) async {
4 months ago
MsgType type = MsgType.values[msgType];
Uint8List data = utf8.encode(content);
if (type == MsgType.IMAGE || type == MsgType.VIDEO || type == MsgType.AUDIO) {
4 months ago
File file = File(attach);
Directory dir = await getTemporaryDirectory();
File newFile = await file.copy("${dir.path}/hx_${attach.split('/').last}");
4 months ago
attach = newFile.path;
}
4 months ago
Map<String, dynamic> message = createMessage(toId, content, fromId: userId, attach: attach, msgType: msgType, replyId: replyId);
4 months ago
message["state"] = 1;
4 months ago
int id = await hxDatabase.insert(message).catchError((error) {
debugPrint("insertMessage: $error");
});
if (!checkSocket()) {
4 months ago
hxDatabase.update({"id": id, "state": 3}).catchError((error) {
debugPrint("insertMessage: ${error.toString()}");
4 months ago
});
message["id"] = id;
message["state"] = 3;
return Message.fromJson(message);
}
4 months ago
message["id"] = id;
4 months ago
MsgData msgData = MsgData(to: toId, from: userId, type: type, data: data);
final proto2 = Proto(5, 1, msgData.writeToBuffer());
try {
_socket.add(proto2.toBytes());
} catch (e) {
hxDatabase.update({"id": id, "state": 3}).catchError((error) {
debugPrint("insertMessage: ${error.toString()}");
});
debugPrint("socket-send-error: ${e.toString()}");
showDebugToast("socket-send-error: ${e.toString()}");
reconnect();
}
4 months ago
return Message.fromJson(message);
}
checkSocket() {
if (_socket == null) {
4 months ago
reconnect();
return false;
}
return true;
}
String get userId => _shared.getString("userId");
}