Bug Fix & Feature

- Fix the breakdown when sending huge files
- Add md5 processing indicator when sending file
This commit is contained in:
Linloir 2022-10-18 17:19:50 +08:00
parent cf9e6b26b5
commit 01568cfe9b
No known key found for this signature in database
GPG Key ID: 58EEB209A0F2C366
7 changed files with 131 additions and 33 deletions

View File

@ -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
); );
var newHistoryList = [newHistory, ...state.chatHistory]; if(msg.type == MessageType.file) {
emit(state.copyWith(chatHistory: newHistoryList)); //Remove mock history
_bindSubscriptionForSending(messageMd5: message.contentmd5); 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];
emit(state.copyWith(chatHistory: newHistoryList));
}
_bindSubscriptionForSending(messageMd5: msg.contentmd5);
} }
Future<void> fetchHistory() async { Future<void> fetchHistory() async {

View File

@ -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;

View File

@ -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,

View File

@ -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,

View File

@ -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);
} }

View File

@ -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;

View File

@ -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 {