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
* @Date : 2022-10-13 14:03:56
* @LastEditTime : 2022-10-18 11:25:04
* @LastEditTime : 2022-10-18 17:09:55
* @Description :
*/
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:bloc/bloc.dart';
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';
import 'package:open_file/open_file.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tcp_client/chat/cubit/chat_state.dart';
import 'package:tcp_client/chat/model/chat_history.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/models/local_file.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/tcp_repository.dart';
@ -34,23 +39,63 @@ class ChatCubit extends Cubit<ChatState> {
final Map<String, StreamSubscription> messageSendSubscriptionMap = {};
final Map<String, StreamSubscription> fileFetchSubscriptionMap = {};
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
localServiceRepository.storeMessages([message]);
localServiceRepository.storeMessages([msg]);
//Send to server
tcpRepository.pushRequest(SendMessageRequest(
message: message,
message: msg,
token: (await SharedPreferences.getInstance()).getInt('token')
));
//Emit new state
var newHistory = ChatHistory(
message: message,
message: msg,
type: ChatHistoryType.outcome,
status: ChatHistoryStatus.sending
);
var newHistoryList = [newHistory, ...state.chatHistory];
emit(state.copyWith(chatHistory: newHistoryList));
_bindSubscriptionForSending(messageMd5: message.contentmd5);
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];
emit(state.copyWith(chatHistory: newHistoryList));
}
_bindSubscriptionForSending(messageMd5: msg.contentmd5);
}
Future<void> fetchHistory() async {

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-14 14:55:20
* @LastEditTime : 2022-10-14 15:26:25
* @LastEditTime : 2022-10-18 15:19:46
* @Description :
*/
@ -9,7 +9,7 @@ import 'package:equatable/equatable.dart';
import 'package:tcp_client/repositories/common_models/message.dart';
enum ChatHistoryType { outcome, income }
enum ChatHistoryStatus { none, sending, downloading, done, failed }
enum ChatHistoryStatus { none, processing, sending, downloading, done, failed }
class ChatHistory extends Equatable {
final Message message;

View File

@ -1,13 +1,14 @@
/*
* @Author : Linloir
* @Date : 2022-10-14 17:07:13
* @LastEditTime : 2022-10-15 11:40:47
* @LastEditTime : 2022-10-18 15:44:34
* @Description :
*/
import 'package:easy_debounce/easy_debounce.dart';
import 'package:flutter/material.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/model/chat_history.dart';
@ -22,7 +23,7 @@ class FileBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
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(
'findfile${history.message.contentmd5}',
const Duration(milliseconds: 500),
@ -49,7 +50,15 @@ class FileBox extends StatelessWidget {
Icons.refresh_rounded,
size: 24,
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(
height: 18.0,
width: 18.0,

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-14 21:57:05
* @LastEditTime : 2022-10-18 11:25:09
* @LastEditTime : 2022-10-18 15:30:09
* @Description :
*/
@ -33,7 +33,6 @@ class MessageInputCubit extends Cubit<MessageInputState> {
targetid: chatCubit.userID,
contenttype: MessageType.plaintext,
content: state.input.value,
token: (await SharedPreferences.getInstance()).getInt('token')!
));
emit(state.copyWith(
status: FormzStatus.pure,

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-14 17:54:30
* @LastEditTime : 2022-10-18 11:25:12
* @LastEditTime : 2022-10-18 15:30:05
* @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/model/input.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 {
InputBox({super.key});
@ -71,10 +72,9 @@ class InputBox extends StatelessWidget {
var newMessage = Message(
userid: (await SharedPreferences.getInstance()).getInt('userid')!,
targetid: chatCubit.userID,
content: basename(file.file.path),
content: basename(file.path),
contenttype: MessageType.file,
payload: file,
token: (await SharedPreferences.getInstance()).getInt('token')!
payload: LocalFile(file: file, filemd5: ""),
);
chatCubit.addMessage(newMessage);
}

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-11 10:30:05
* @LastEditTime : 2022-10-18 14:20:02
* @LastEditTime : 2022-10-18 16:53:04
* @Description :
*/
@ -35,13 +35,31 @@ class Message extends JSONEncodable {
late final String? _filemd5;
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({
required int userid,
required int targetid,
required MessageType contenttype,
required String content,
LocalFile? payload,
required int token
LocalFile? payload
}):
_userid = userid,
_targetid = targetid,
@ -70,6 +88,32 @@ class Message extends JSONEncodable {
_contentmd5 = jsonObject['md5encoded'] as String,
_filemd5 = jsonObject['filemd5'] as String?,
_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 recieverID => _targetid;

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @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
*/
@ -109,23 +109,24 @@ class LocalServiceRepository {
}
//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(
type: fileType,
allowMultiple: false,
);
if (filePickResult == null) return null;
var file = File(filePickResult.files.single.path!);
var md5Output = AccumulatorSink<Digest>();
ByteConversionSink md5Input = md5.startChunkedConversion(md5Output);
await for(var bytes in file.openRead()) {
md5Input.add(bytes);
}
md5Input.close();
return LocalFile(
file: file,
filemd5: md5Output.events.single.toString()
);
// var md5Output = AccumulatorSink<Digest>();
// ByteConversionSink md5Input = md5.startChunkedConversion(md5Output);
// await for(var bytes in file.openRead()) {
// md5Input.add(bytes);
// }
// md5Input.close();
// return LocalFile(
// file: file,
// filemd5: md5Output.events.single.toString()
// );
return file;
}
Future<void> storeMessages(List<Message> messages) async {