mirror of
https://github.com/Linloir/Simple-TCP-Client.git
synced 2025-12-18 17:28:11 +08:00
Bug Fix & Feature
- Fix the breakdown when sending huge files - Add md5 processing indicator when sending file
This commit is contained in:
parent
cf9e6b26b5
commit
01568cfe9b
@ -1,19 +1,24 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 14:03:56
|
* @Date : 2022-10-13 14:03:56
|
||||||
* @LastEditTime : 2022-10-18 11:25:04
|
* @LastEditTime : 2022-10-18 17:09:55
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:convert/convert.dart';
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:open_file/open_file.dart';
|
import 'package:open_file/open_file.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:tcp_client/chat/cubit/chat_state.dart';
|
import 'package:tcp_client/chat/cubit/chat_state.dart';
|
||||||
import 'package:tcp_client/chat/model/chat_history.dart';
|
import 'package:tcp_client/chat/model/chat_history.dart';
|
||||||
import 'package:tcp_client/repositories/common_models/message.dart';
|
import 'package:tcp_client/repositories/common_models/message.dart';
|
||||||
import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart';
|
import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart';
|
||||||
|
import 'package:tcp_client/repositories/local_service_repository/models/local_file.dart';
|
||||||
import 'package:tcp_client/repositories/tcp_repository/models/tcp_request.dart';
|
import 'package:tcp_client/repositories/tcp_repository/models/tcp_request.dart';
|
||||||
import 'package:tcp_client/repositories/tcp_repository/models/tcp_response.dart';
|
import 'package:tcp_client/repositories/tcp_repository/models/tcp_response.dart';
|
||||||
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
|
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
|
||||||
@ -34,23 +39,63 @@ class ChatCubit extends Cubit<ChatState> {
|
|||||||
final Map<String, StreamSubscription> messageSendSubscriptionMap = {};
|
final Map<String, StreamSubscription> messageSendSubscriptionMap = {};
|
||||||
final Map<String, StreamSubscription> fileFetchSubscriptionMap = {};
|
final Map<String, StreamSubscription> fileFetchSubscriptionMap = {};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Future<void> addMessage(Message message) async {
|
Future<void> addMessage(Message message) async {
|
||||||
|
var msg = message;
|
||||||
|
if(msg.type == MessageType.file) {
|
||||||
|
//wait until md5 is converted
|
||||||
|
//Emit new state
|
||||||
|
var newHistory = ChatHistory(
|
||||||
|
message: msg,
|
||||||
|
type: ChatHistoryType.outcome,
|
||||||
|
status: ChatHistoryStatus.processing
|
||||||
|
);
|
||||||
|
var newHistoryList = [newHistory, ...state.chatHistory];
|
||||||
|
emit(state.copyWith(chatHistory: newHistoryList));
|
||||||
|
var file = msg.payload!.file;
|
||||||
|
var md5Output = AccumulatorSink<Digest>();
|
||||||
|
ByteConversionSink md5Input = md5.startChunkedConversion(md5Output);
|
||||||
|
await for(var bytes in file.openRead()) {
|
||||||
|
md5Input.add(bytes);
|
||||||
|
}
|
||||||
|
md5Input.close();
|
||||||
|
var loadedFile = LocalFile(
|
||||||
|
file: file,
|
||||||
|
filemd5: md5Output.events.single.toString()
|
||||||
|
);
|
||||||
|
msg = msg.copyWith(
|
||||||
|
payload: loadedFile
|
||||||
|
);
|
||||||
|
}
|
||||||
//Store locally
|
//Store locally
|
||||||
localServiceRepository.storeMessages([message]);
|
localServiceRepository.storeMessages([msg]);
|
||||||
//Send to server
|
//Send to server
|
||||||
tcpRepository.pushRequest(SendMessageRequest(
|
tcpRepository.pushRequest(SendMessageRequest(
|
||||||
message: message,
|
message: msg,
|
||||||
token: (await SharedPreferences.getInstance()).getInt('token')
|
token: (await SharedPreferences.getInstance()).getInt('token')
|
||||||
));
|
));
|
||||||
//Emit new state
|
//Emit new state
|
||||||
var newHistory = ChatHistory(
|
var newHistory = ChatHistory(
|
||||||
message: message,
|
message: msg,
|
||||||
type: ChatHistoryType.outcome,
|
type: ChatHistoryType.outcome,
|
||||||
status: ChatHistoryStatus.sending
|
status: ChatHistoryStatus.sending
|
||||||
);
|
);
|
||||||
|
if(msg.type == MessageType.file) {
|
||||||
|
//Remove mock history
|
||||||
|
var newHistoryList = [...state.chatHistory];
|
||||||
|
var index = newHistoryList.indexWhere((element) => element.message.contentmd5 == msg.contentmd5);
|
||||||
|
if(index == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newHistoryList[index] = newHistory;
|
||||||
|
emit(state.copyWith(chatHistory: newHistoryList));
|
||||||
|
}
|
||||||
|
else {
|
||||||
var newHistoryList = [newHistory, ...state.chatHistory];
|
var newHistoryList = [newHistory, ...state.chatHistory];
|
||||||
emit(state.copyWith(chatHistory: newHistoryList));
|
emit(state.copyWith(chatHistory: newHistoryList));
|
||||||
_bindSubscriptionForSending(messageMd5: message.contentmd5);
|
}
|
||||||
|
_bindSubscriptionForSending(messageMd5: msg.contentmd5);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fetchHistory() async {
|
Future<void> fetchHistory() async {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-14 14:55:20
|
* @Date : 2022-10-14 14:55:20
|
||||||
* @LastEditTime : 2022-10-14 15:26:25
|
* @LastEditTime : 2022-10-18 15:19:46
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ import 'package:equatable/equatable.dart';
|
|||||||
import 'package:tcp_client/repositories/common_models/message.dart';
|
import 'package:tcp_client/repositories/common_models/message.dart';
|
||||||
|
|
||||||
enum ChatHistoryType { outcome, income }
|
enum ChatHistoryType { outcome, income }
|
||||||
enum ChatHistoryStatus { none, sending, downloading, done, failed }
|
enum ChatHistoryStatus { none, processing, sending, downloading, done, failed }
|
||||||
|
|
||||||
class ChatHistory extends Equatable {
|
class ChatHistory extends Equatable {
|
||||||
final Message message;
|
final Message message;
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-14 17:07:13
|
* @Date : 2022-10-14 17:07:13
|
||||||
* @LastEditTime : 2022-10-15 11:40:47
|
* @LastEditTime : 2022-10-18 15:44:34
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:easy_debounce/easy_debounce.dart';
|
import 'package:easy_debounce/easy_debounce.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:loading_indicator/loading_indicator.dart';
|
||||||
import 'package:tcp_client/chat/cubit/chat_cubit.dart';
|
import 'package:tcp_client/chat/cubit/chat_cubit.dart';
|
||||||
import 'package:tcp_client/chat/model/chat_history.dart';
|
import 'package:tcp_client/chat/model/chat_history.dart';
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ class FileBox extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: (history.status == ChatHistoryStatus.downloading || history.status == ChatHistoryStatus.sending) ? null : () {
|
onTap: (history.status == ChatHistoryStatus.downloading || history.status == ChatHistoryStatus.sending || history.status == ChatHistoryStatus.processing) ? null : () {
|
||||||
EasyDebounce.debounce(
|
EasyDebounce.debounce(
|
||||||
'findfile${history.message.contentmd5}',
|
'findfile${history.message.contentmd5}',
|
||||||
const Duration(milliseconds: 500),
|
const Duration(milliseconds: 500),
|
||||||
@ -49,6 +50,14 @@ class FileBox extends StatelessWidget {
|
|||||||
Icons.refresh_rounded,
|
Icons.refresh_rounded,
|
||||||
size: 24,
|
size: 24,
|
||||||
color: history.type == ChatHistoryType.income ? Colors.red[800] : Colors.white.withOpacity(0.8),
|
color: history.type == ChatHistoryType.income ? Colors.red[800] : Colors.white.withOpacity(0.8),
|
||||||
|
) : history.status == ChatHistoryStatus.processing ?
|
||||||
|
SizedBox(
|
||||||
|
height: 18.0,
|
||||||
|
width: 18.0,
|
||||||
|
child: LoadingIndicator(
|
||||||
|
indicatorType: Indicator.ballPulseSync,
|
||||||
|
colors: [Colors.white.withOpacity(0.8)],
|
||||||
|
),
|
||||||
) :
|
) :
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 18.0,
|
height: 18.0,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-14 21:57:05
|
* @Date : 2022-10-14 21:57:05
|
||||||
* @LastEditTime : 2022-10-18 11:25:09
|
* @LastEditTime : 2022-10-18 15:30:09
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -33,7 +33,6 @@ class MessageInputCubit extends Cubit<MessageInputState> {
|
|||||||
targetid: chatCubit.userID,
|
targetid: chatCubit.userID,
|
||||||
contenttype: MessageType.plaintext,
|
contenttype: MessageType.plaintext,
|
||||||
content: state.input.value,
|
content: state.input.value,
|
||||||
token: (await SharedPreferences.getInstance()).getInt('token')!
|
|
||||||
));
|
));
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
status: FormzStatus.pure,
|
status: FormzStatus.pure,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-14 17:54:30
|
* @Date : 2022-10-14 17:54:30
|
||||||
* @LastEditTime : 2022-10-18 11:25:12
|
* @LastEditTime : 2022-10-18 15:30:05
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -16,6 +16,7 @@ import 'package:tcp_client/chat/view/input_box/cubit/input_cubit.dart';
|
|||||||
import 'package:tcp_client/chat/view/input_box/cubit/input_state.dart';
|
import 'package:tcp_client/chat/view/input_box/cubit/input_state.dart';
|
||||||
import 'package:tcp_client/chat/view/input_box/model/input.dart';
|
import 'package:tcp_client/chat/view/input_box/model/input.dart';
|
||||||
import 'package:tcp_client/repositories/common_models/message.dart';
|
import 'package:tcp_client/repositories/common_models/message.dart';
|
||||||
|
import 'package:tcp_client/repositories/local_service_repository/models/local_file.dart';
|
||||||
|
|
||||||
class InputBox extends StatelessWidget {
|
class InputBox extends StatelessWidget {
|
||||||
InputBox({super.key});
|
InputBox({super.key});
|
||||||
@ -71,10 +72,9 @@ class InputBox extends StatelessWidget {
|
|||||||
var newMessage = Message(
|
var newMessage = Message(
|
||||||
userid: (await SharedPreferences.getInstance()).getInt('userid')!,
|
userid: (await SharedPreferences.getInstance()).getInt('userid')!,
|
||||||
targetid: chatCubit.userID,
|
targetid: chatCubit.userID,
|
||||||
content: basename(file.file.path),
|
content: basename(file.path),
|
||||||
contenttype: MessageType.file,
|
contenttype: MessageType.file,
|
||||||
payload: file,
|
payload: LocalFile(file: file, filemd5: ""),
|
||||||
token: (await SharedPreferences.getInstance()).getInt('token')!
|
|
||||||
);
|
);
|
||||||
chatCubit.addMessage(newMessage);
|
chatCubit.addMessage(newMessage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-11 10:30:05
|
* @Date : 2022-10-11 10:30:05
|
||||||
* @LastEditTime : 2022-10-18 14:20:02
|
* @LastEditTime : 2022-10-18 16:53:04
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -35,13 +35,31 @@ class Message extends JSONEncodable {
|
|||||||
late final String? _filemd5;
|
late final String? _filemd5;
|
||||||
final LocalFile? _payload;
|
final LocalFile? _payload;
|
||||||
|
|
||||||
|
Message._internal({
|
||||||
|
required int userid,
|
||||||
|
required int targetid,
|
||||||
|
required MessageType contenttype,
|
||||||
|
required String content,
|
||||||
|
required int timestamp,
|
||||||
|
required String contentmd5,
|
||||||
|
required String? filemd5,
|
||||||
|
required LocalFile? payload
|
||||||
|
}):
|
||||||
|
_userid = userid,
|
||||||
|
_targetid = targetid,
|
||||||
|
_contenttype = contenttype,
|
||||||
|
_content = content,
|
||||||
|
_timestamp = timestamp,
|
||||||
|
_contentmd5 = contentmd5,
|
||||||
|
_filemd5 = filemd5,
|
||||||
|
_payload = payload;
|
||||||
|
|
||||||
Message({
|
Message({
|
||||||
required int userid,
|
required int userid,
|
||||||
required int targetid,
|
required int targetid,
|
||||||
required MessageType contenttype,
|
required MessageType contenttype,
|
||||||
required String content,
|
required String content,
|
||||||
LocalFile? payload,
|
LocalFile? payload
|
||||||
required int token
|
|
||||||
}):
|
}):
|
||||||
_userid = userid,
|
_userid = userid,
|
||||||
_targetid = targetid,
|
_targetid = targetid,
|
||||||
@ -71,6 +89,32 @@ class Message extends JSONEncodable {
|
|||||||
_filemd5 = jsonObject['filemd5'] as String?,
|
_filemd5 = jsonObject['filemd5'] as String?,
|
||||||
_payload = payload;
|
_payload = payload;
|
||||||
|
|
||||||
|
Message copyWith({
|
||||||
|
int? userid,
|
||||||
|
int? targetid,
|
||||||
|
MessageType? contenttype,
|
||||||
|
String? content,
|
||||||
|
int? timestamp,
|
||||||
|
LocalFile? payload
|
||||||
|
}) {
|
||||||
|
return Message._internal(
|
||||||
|
userid: userid ?? _userid,
|
||||||
|
targetid: targetid ?? _targetid,
|
||||||
|
contenttype: contenttype ?? _contenttype,
|
||||||
|
content: content == null ? _content : base64.encode(utf8.encode(content)),
|
||||||
|
timestamp: timestamp ?? _timestamp,
|
||||||
|
contentmd5: content != null || userid != null || targetid != null ?
|
||||||
|
md5.convert(
|
||||||
|
utf8.encode(content ?? contentDecoded).toList()
|
||||||
|
..addAll(Uint8List(4)..buffer.asInt32List()[0] = userid ?? _userid)
|
||||||
|
..addAll(Uint8List(4)..buffer.asInt32List()[0] = targetid ?? _targetid)
|
||||||
|
..addAll(Uint8List(4)..buffer.asInt32List()[0] = timestamp ?? _timestamp)
|
||||||
|
).toString() : _contentmd5,
|
||||||
|
filemd5: payload?.filemd5 ?? _filemd5,
|
||||||
|
payload: payload ?? _payload
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
int get senderID => _userid;
|
int get senderID => _userid;
|
||||||
int get recieverID => _targetid;
|
int get recieverID => _targetid;
|
||||||
MessageType get type => _contenttype;
|
MessageType get type => _contenttype;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-11 10:56:02
|
* @Date : 2022-10-11 10:56:02
|
||||||
* @LastEditTime : 2022-10-18 14:35:25
|
* @LastEditTime : 2022-10-18 15:13:27
|
||||||
* @Description : Local Service Repository
|
* @Description : Local Service Repository
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -109,23 +109,24 @@ class LocalServiceRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Calls on the system to open the file
|
//Calls on the system to open the file
|
||||||
Future<LocalFile?> pickFile(FileType fileType) async {
|
Future<File?> pickFile(FileType fileType) async {
|
||||||
var filePickResult = await FilePicker.platform.pickFiles(
|
var filePickResult = await FilePicker.platform.pickFiles(
|
||||||
type: fileType,
|
type: fileType,
|
||||||
allowMultiple: false,
|
allowMultiple: false,
|
||||||
);
|
);
|
||||||
if (filePickResult == null) return null;
|
if (filePickResult == null) return null;
|
||||||
var file = File(filePickResult.files.single.path!);
|
var file = File(filePickResult.files.single.path!);
|
||||||
var md5Output = AccumulatorSink<Digest>();
|
// var md5Output = AccumulatorSink<Digest>();
|
||||||
ByteConversionSink md5Input = md5.startChunkedConversion(md5Output);
|
// ByteConversionSink md5Input = md5.startChunkedConversion(md5Output);
|
||||||
await for(var bytes in file.openRead()) {
|
// await for(var bytes in file.openRead()) {
|
||||||
md5Input.add(bytes);
|
// md5Input.add(bytes);
|
||||||
}
|
// }
|
||||||
md5Input.close();
|
// md5Input.close();
|
||||||
return LocalFile(
|
// return LocalFile(
|
||||||
file: file,
|
// file: file,
|
||||||
filemd5: md5Output.events.single.toString()
|
// filemd5: md5Output.events.single.toString()
|
||||||
);
|
// );
|
||||||
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeMessages(List<Message> messages) async {
|
Future<void> storeMessages(List<Message> messages) async {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user