diff --git a/assets/images/loading.gif b/assets/images/loading.gif new file mode 100644 index 0000000..8052b1b Binary files /dev/null and b/assets/images/loading.gif differ diff --git a/lib/database/box_type.dart b/lib/database/box_type.dart index f356e3a..89b729a 100644 --- a/lib/database/box_type.dart +++ b/lib/database/box_type.dart @@ -39,7 +39,7 @@ class ChatSetting { @HiveType(typeId: 1) class MessageT { @HiveField(0) - int msgId; + String msgId; @HiveField(1) String senderId; @@ -70,9 +70,37 @@ class MessageT { ); } +@HiveType(typeId: 2) +class AttachmentProgress { + // denote the number of chunk has sent or recieved. + // if 0, means no chunk has received or sent. + @HiveField(0) + int hasChunkNum; + + @HiveField(1) + int totalChunkNum; + + @HiveField(2) + double progress; + + @HiveField(3) + bool isValid; + + @HiveField(4) + bool isPause; + + AttachmentProgress( + this.hasChunkNum, + this.totalChunkNum, + this.progress, + this.isValid, + this.isPause, + ); +} + /// message type: /// 1. text/multipart: text with/without images or videos /// 2. voice /// 3. video /// 4. voice-call -/// 5. video-call \ No newline at end of file +/// 5. video-call diff --git a/lib/database/box_type.g.dart b/lib/database/box_type.g.dart index b98d13b..7eacd31 100644 --- a/lib/database/box_type.g.dart +++ b/lib/database/box_type.g.dart @@ -69,7 +69,7 @@ class MessageTAdapter extends TypeAdapter { for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), }; return MessageT( - fields[0] as int, + fields[0] as String, fields[1] as String, fields[2] == null ? '' : fields[2] as String, fields[3] as String, @@ -109,3 +109,49 @@ class MessageTAdapter extends TypeAdapter { runtimeType == other.runtimeType && typeId == other.typeId; } + +class AttachmentProgressAdapter extends TypeAdapter { + @override + final int typeId = 2; + + @override + AttachmentProgress read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return AttachmentProgress( + fields[0] as int, + fields[1] as int, + fields[2] as double, + fields[3] as bool, + fields[4] as bool, + ); + } + + @override + void write(BinaryWriter writer, AttachmentProgress obj) { + writer + ..writeByte(5) + ..writeByte(0) + ..write(obj.hasChunkNum) + ..writeByte(1) + ..write(obj.totalChunkNum) + ..writeByte(2) + ..write(obj.progress) + ..writeByte(3) + ..write(obj.isValid) + ..writeByte(4) + ..write(obj.isPause); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AttachmentProgressAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/database/hive_database.dart b/lib/database/hive_database.dart index b14b19a..1fc2603 100644 --- a/lib/database/hive_database.dart +++ b/lib/database/hive_database.dart @@ -32,14 +32,19 @@ class HiveDatabase { encryptionCipher: HiveAesCipher(encryptionKeyUint8List), compactionStrategy: (entries, deletedEntries) => entries > 200, ); + await Hive.openBox('msg_index_${chatBox.contactId}'); } + await Hive.openBox('attachment_send'); + await Hive.openBox('attachment_receive'); + _isInitialised = true; } static void registerAdapter() { Hive.registerAdapter(ChatSettingAdapter()); Hive.registerAdapter(MessageTAdapter()); + Hive.registerAdapter(AttachmentProgressAdapter()); } static Future> openMessageBox(String contactId) async { diff --git a/lib/models/websocket_model.dart b/lib/models/websocket_model.dart index 39fcb78..8980864 100644 --- a/lib/models/websocket_model.dart +++ b/lib/models/websocket_model.dart @@ -1,20 +1,14 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; -import 'package:together_mobile/database/hive_database.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:web_socket_channel/status.dart' as status; -import 'package:together_mobile/models/route_state_model.dart'; import 'package:together_mobile/database/box_type.dart'; -import 'package:together_mobile/models/apply_list_model.dart'; -import 'package:together_mobile/models/contact_model.dart'; -import 'package:together_mobile/models/init_get_it.dart'; -import 'package:together_mobile/models/user_model.dart'; -import 'package:together_mobile/notification_api.dart'; +import 'package:together_mobile/utils/ws_receive_callback.dart'; enum SocketStatus { connected, @@ -23,6 +17,8 @@ enum SocketStatus { closed, } +// const chunkSize = 1024 * 1024; + class WebSocketManager extends ChangeNotifier { late Uri wsUrl; late WebSocketChannel channel; @@ -35,6 +31,7 @@ class WebSocketManager extends ChangeNotifier { int reconnectTimes = 0; Duration heartBeatTimeout = const Duration(seconds: 4); Duration reconnectTimeout = const Duration(seconds: 3); + Map sendImageTimer = {}; void connect(String userId, bool isReconnect) { id = userId; @@ -66,7 +63,7 @@ class WebSocketManager extends ChangeNotifier { reconnectTimes = 0; } - void onData(jsonData) { + void onData(jsonData) async { // If socket can receive msg, that means connection is estabilished socketStatus = SocketStatus.connected; notifyListeners(); @@ -79,10 +76,12 @@ class WebSocketManager extends ChangeNotifier { heartBeatInspect(); - Map data = json.decode(jsonData); + // Map data = json.decode(jsonData); + Map data = + await compute((message) => json.decode(message), jsonData); switch (data['event']) { case 'friend-chat-msg': - receiveFriendMsg(data, true); + await receiveFriendMsg(data, true); case 'apply-friend': receiveApplyFriend(data); case 'friend-added': @@ -95,6 +94,10 @@ class WebSocketManager extends ChangeNotifier { receiveGroupChatCreation(data); case 'group-chat-msg': receiveGroupChatMsg(data, true); + case 'pull-chat-image': + receivePullChatImage(data); + case 'chat-image-send-ok': + receiveCheckChatImage(data); } } @@ -198,245 +201,50 @@ class WebSocketManager extends ChangeNotifier { } }); } -} -void receiveFriendMsg(Map msg, bool isShowNotification) async { - print('=================收到了好友信息事件=================='); - print(msg); - print('======================================='); - String senderId = msg['senderId'] as String; - Box chatSettingBox = Hive.box('chat_setting'); - Box messageTBox = await HiveDatabase.openMessageBox(senderId); - ChatSetting? chatSetting = chatSettingBox.get(senderId); - DateTime dateTime = DateTime.parse(msg['dateTime'] as String); - - if (chatSetting == null) { - chatSettingBox.put( - senderId, - ChatSetting( - senderId, - 0, - false, - true, - false, - dateTime, - 1, - ), - ); - } else { - chatSetting.isOpen = true; - chatSetting.latestDateTime = dateTime; - chatSetting.unreadCount++; - chatSettingBox.put(senderId, chatSetting); - } - - List attachments = List.from(msg['attachments']); - final DateTime now = DateTime.parse(msg['dateTime'] as String); - - messageTBox.add( - MessageT( - msg['msgId'] as int, - senderId, - msg['text'], - msg['type'], - now, - msg['isShowTime'], - attachments, - ), - ); - - if (!isShowNotification) { - return; - } - - String name = getIt.get().friends[senderId]!.friendRemark.isEmpty - ? getIt.get().friends[senderId]!.nickname - : getIt.get().friends[senderId]!.friendRemark; - - String routeName = getIt.get().currentPathName; - - String avatar = getIt.get().friends[senderId]!.avatar; - - if (!getIt.get().isVisible) { - NotificationAPI.showMessageNotifications( - senderId: senderId, - name: name, - avatar: avatar, - text: msg['text'] as String, - ); - } else if (routeName == 'Message') { - if (!getIt.get().query.containsValue(senderId)) { - NotificationAPI.showMessageNotifications( - senderId: senderId, - name: name, - avatar: avatar, - text: msg['text'] as String, - ); + void addSendImageTimer(String filename, int totalChunkNum) { + if (sendImageTimer.containsKey(filename)) { + sendImageTimer[filename]!.cancel(); + sendImageTimer.remove(filename); } - } else if (routeName != 'Chat') { - NotificationAPI.showMessageNotifications( - senderId: senderId, - name: name, - avatar: avatar, - text: msg['text'] as String, + + sendImageTimer[filename] = Timer( + heartBeatTimeout, + () async { + late Box attachmentLoadingBox; + try { + attachmentLoadingBox = + Hive.box('attachment_receive'); + } catch (_) { + attachmentLoadingBox = + await Hive.openBox('attachment_receive'); + } + + AttachmentProgress? at = attachmentLoadingBox.get(filename); + + if (at == null) { + attachmentLoadingBox.put( + filename, + AttachmentProgress( + 0, + totalChunkNum, + 0, + false, + true, + ), + ); + } else { + at.isPause = true; + attachmentLoadingBox.put(filename, at); + } + }, ); } -} -void receiveApplyFriend(Map msg) { - print('=================收到了申请好友事件=================='); - print(msg); - print('======================================='); - getIt.get().addJson(msg); -} - -void receiveFriendAdded(Map msg) { - print('=================收到了申请好友通过事件=================='); - print(msg); - print('======================================='); - getIt.get().addFriend(msg['friendId'], msg['setting']); - getIt - .get() - .addFriendAccountProfile(msg['friendId'], msg['accountProfile']); -} - -void receiveFriendDeleted(Map msg) { - print('=================收到了解除好友事件=================='); - print(msg); - print('======================================='); - getIt.get().removeFriend(msg['friendId']); - getIt.get().removeFriend(msg['friendId']); -} - -void receiveChatImages(Map msg) async { - print('=================收到了聊天图片事件=================='); - print(msg); - print('======================================='); - String chatImageDir = getIt.get().baseImageDir; - File file = File('$chatImageDir/${msg['filename']}'); - if (await file.exists()) { - return; - } else { - await file.create(recursive: true); - await file.writeAsBytes(List.from(msg['bytes'])); - } -} - -void receiveGroupChatCreation(Map msg) { - print('=================收到了群聊邀请事件=================='); - print(msg); - print('======================================='); - String groupChatId = msg['groupChat']['id']; - getIt.get().addGroupChat(groupChatId); - getIt.get().addGroupChatProfile(groupChatId, msg); -} - -void receiveGroupChatMsg( - Map msg, bool isShowNotification) async { - print('=================收到了群聊信息事件=================='); - print(msg); - print('======================================='); - String senderId = msg['senderId'] as String; - String groupChatId = msg['groupChatId'] as String; - Box chatSettingBox = Hive.box('chat_setting'); - Box messageTBox = await HiveDatabase.openMessageBox(groupChatId); - ChatSetting? chatSetting = chatSettingBox.get(groupChatId); - DateTime dateTime = DateTime.parse(msg['dateTime'] as String); - - getIt.get().addGroupChatMemberProfile( - groupChatId, - msg['senderId'], - { - 'avatar': msg['avatar'], - 'nickname': msg['nickname'], - 'remarkInGroupChat': msg['remarkInGroupChat'], - }, - ); - - if (chatSetting == null) { - chatSettingBox.put( - groupChatId, - ChatSetting( - groupChatId, - 1, - false, - true, - false, - dateTime, - 1, - ), - ); - } else { - chatSetting.isOpen = true; - chatSetting.latestDateTime = dateTime; - chatSetting.unreadCount++; - chatSettingBox.put(groupChatId, chatSetting); - } - - List attachments = List.from(msg['attachments']); - final DateTime now = DateTime.parse(msg['dateTime'] as String); - - messageTBox.add( - MessageT( - msg['msgId'] as int, - senderId, - msg['text'], - msg['type'], - now, - msg['isShowTime'], - attachments, - ), - ); - - if (!isShowNotification) { - return; - } - - String avatar = - getIt.get().groupChats[groupChatId]!.avatar; - late String name; - - if (getIt.get().friends.containsKey(senderId)) { - if (getIt.get().friends[senderId]!.friendRemark.isNotEmpty) { - name = getIt.get().friends[senderId]!.friendRemark; - } else if ((msg['remarkInGroupChat'] as String).isNotEmpty) { - name = msg['remarkInGroupChat']; - } else { - name = msg['nickname']; + void removeSendImageTimer(String filename) { + if (sendImageTimer.containsKey(filename)) { + sendImageTimer[filename]!.cancel(); } - } else { - name = (msg['remarkInGroupChat'] as String).isNotEmpty - ? msg['remarkInGroupChat'] - : msg['nickname']; - } - - String routeName = getIt.get().currentPathName; - - if (!getIt.get().isVisible) { - NotificationAPI.showMessageNotifications( - senderId: senderId, - name: name, - avatar: avatar, - text: msg['text'] as String, - groupChatId: groupChatId, - ); - } else if (routeName == 'Message') { - if (!getIt.get().query.containsValue(senderId)) { - NotificationAPI.showMessageNotifications( - senderId: senderId, - name: name, - avatar: avatar, - text: msg['text'] as String, - groupChatId: groupChatId, - ); - } - } else if (routeName != 'Chat') { - NotificationAPI.showMessageNotifications( - senderId: senderId, - name: name, - avatar: avatar, - text: msg['text'] as String, - groupChatId: groupChatId, - ); + sendImageTimer.remove(filename); } } diff --git a/lib/request/message.dart b/lib/request/message.dart index a749df1..d014f0d 100644 --- a/lib/request/message.dart +++ b/lib/request/message.dart @@ -12,3 +12,14 @@ Future> getUnreceivedMsg(String userId) async { return response.data; } + +Future> uploadChatAttachment( + Map data, +) async { + Response response = await request.post( + '/message/attachment', + data: data, + ); + + return response.data; +} diff --git a/lib/router/chat_router.dart b/lib/router/chat_router.dart index d33ee9d..32fe679 100644 --- a/lib/router/chat_router.dart +++ b/lib/router/chat_router.dart @@ -39,12 +39,9 @@ final chatRouter = GoRoute( parentNavigatorKey: rootNavigatorKey, builder: (context, state) { Map extra = state.extra as Map; - for (var a in extra['attachmentItems']! as List) { - print(a.id); - } return ImageViewScreen( - attachmentItems: - extra['attachmentItems']! as List, + attachments: + extra['attachments']! as List, initialIndex: extra['index']! as int, ); }, diff --git a/lib/screens/chat/chat_screen.dart b/lib/screens/chat/chat_screen.dart index 4d7fd0d..9f10dd2 100755 --- a/lib/screens/chat/chat_screen.dart +++ b/lib/screens/chat/chat_screen.dart @@ -10,6 +10,7 @@ import 'package:together_mobile/database/hive_database.dart'; import 'package:together_mobile/request/message.dart'; import 'package:together_mobile/screens/chat/components/group_chat_chat_tile.dart'; +import 'package:together_mobile/utils/ws_receive_callback.dart'; import 'components/friend_chat_tile.dart'; import 'components/add_menu.dart'; import 'package:together_mobile/database/box_type.dart'; diff --git a/lib/screens/friend_profile/components/row_floating_buttons.dart b/lib/screens/friend_profile/components/row_floating_buttons.dart index b59ec76..7c5dca4 100755 --- a/lib/screens/friend_profile/components/row_floating_buttons.dart +++ b/lib/screens/friend_profile/components/row_floating_buttons.dart @@ -1,10 +1,8 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:hive/hive.dart'; import 'package:together_mobile/common/constants.dart'; -import 'package:together_mobile/database/box_type.dart'; import 'package:together_mobile/database/hive_database.dart'; class RowFloatingButtons extends StatelessWidget { @@ -75,7 +73,7 @@ class RowFloatingButtons extends StatelessWidget { ), FloatingActionButton( onPressed: () async { - HiveDatabase.openMessageBox(friendId); + await HiveDatabase.openMessageBox(friendId); // ignore: use_build_context_synchronously context.pushReplacementNamed( 'Message', diff --git a/lib/screens/message/components/attachment_container.dart b/lib/screens/message/components/attachment_container.dart new file mode 100644 index 0000000..4a27462 --- /dev/null +++ b/lib/screens/message/components/attachment_container.dart @@ -0,0 +1,119 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:together_mobile/common/constants.dart'; +import 'package:together_mobile/database/box_type.dart'; +import 'package:together_mobile/models/init_get_it.dart'; +import 'package:together_mobile/models/user_model.dart'; + +class AttachmentContainer extends StatefulWidget { + const AttachmentContainer({ + super.key, + required this.isMyself, + required this.filename, + required this.onTapImage, + }); + + final bool isMyself; + final String filename; + final Function(String) onTapImage; + + @override + State createState() => _AttachmentContainerState(); +} + +class _AttachmentContainerState extends State + with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { + late AnimationController _controller; + late Animation _animation; + + @override + bool get wantKeepAlive => true; + + @override + void initState() { + super.initState(); + _controller = AnimationController(vsync: this); + _animation = Tween(begin: 0.0, end: 1.0).animate(_controller); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + super.build(context); + + return Container( + margin: const EdgeInsets.only( + bottom: 15, + ), + constraints: const BoxConstraints( + maxHeight: 150, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.0), + ), + child: ValueListenableBuilder( + valueListenable: widget.isMyself + ? Hive.box('attachment_send') + .listenable(keys: [widget.filename]) + : Hive.box('attachment_receive') + .listenable(keys: [widget.filename]), + builder: (context, apBox, _) { + double progress = apBox.get(widget.filename)!.progress; + String filePath = + '${getIt.get().baseImageDir}/${widget.filename}'; + bool isFileExists = File(filePath).existsSync(); + + _controller.animateTo( + progress, + duration: const Duration(milliseconds: 500), + ); + return Stack( + children: [ + GestureDetector( + onTap: () { + widget.onTapImage(widget.filename); + }, + child: progress == 1.0 || isFileExists + ? Image.file(File(filePath)) + : Image.asset('assets/images/loading.gif'), + ), + if (progress < 1) + Positioned.fill( + child: Stack( + alignment: Alignment.center, + children: [ + Container( + alignment: Alignment.center, + constraints: const BoxConstraints.expand(), + color: const Color.fromARGB(255, 32, 32, 32) + .withOpacity(0.3), + child: Text( + '${(_animation.value * 100).toStringAsFixed(0)}%', + style: const TextStyle( + fontSize: 20, + color: kUnAvailableColor, + ), + ), + ), + CircularProgressIndicator( + value: _animation.value, + color: kSecondaryColor, + strokeWidth: 4, + ), + ], + ), + ), + ], + ); + }, + ), + ); + } +} diff --git a/lib/screens/message/components/friend_message_bubble.dart b/lib/screens/message/components/friend_message_bubble.dart index 65834a5..3db6fab 100644 --- a/lib/screens/message/components/friend_message_bubble.dart +++ b/lib/screens/message/components/friend_message_bubble.dart @@ -1,6 +1,3 @@ -import 'dart:async'; -import 'dart:io'; - import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -12,7 +9,7 @@ import 'package:together_mobile/models/contact_model.dart'; import 'package:together_mobile/models/init_get_it.dart'; import 'package:together_mobile/models/user_model.dart'; import 'package:together_mobile/request/server.dart'; -import 'package:together_mobile/screens/message/image_view_screen/image_view_screen.dart'; +import 'package:together_mobile/screens/message/components/attachment_container.dart'; class FriendMessageBubble extends StatefulWidget { const FriendMessageBubble({ @@ -26,14 +23,13 @@ class FriendMessageBubble extends StatefulWidget { required this.isShowTime, required this.text, required this.attachments, - required this.attachmentItems, + required this.allAttachments, }); final int index, length; final String contactId, senderId, type, dateTime, text; final bool isShowTime; - final List attachments; - final List attachmentItems; + final List attachments, allAttachments; @override State createState() => _FriendMessageBubbleState(); @@ -41,65 +37,14 @@ class FriendMessageBubble extends StatefulWidget { class _FriendMessageBubbleState extends State { // add late here so you can access widget instance - List _isImagesLoaded = []; - final List _timerList = []; late bool _isHideMsg; @override void initState() { super.initState(); - _isImagesLoaded = List.filled(widget.attachments.length, false); - - for (var i = 0; i < widget.attachments.length; i++) { - String imagePath = - '${getIt.get().baseImageDir}/${widget.attachments[i]}'; - File file = File(imagePath); - if (file.existsSync()) { - _isImagesLoaded[i] = true; - } else { - _isImagesLoaded[i] = false; - _timerList.add( - Timer.periodic( - const Duration(milliseconds: 200), - (timer) { - if ((file.existsSync())) { - setState(() { - _isImagesLoaded[i] = true; - }); - timer.cancel(); - } - }, - ), - ); - } - } - final chatSettingBox = Hive.box('chat_setting'); late final chatSetting = chatSettingBox.get(widget.contactId); _isHideMsg = chatSetting!.isHideMsg; - - if (_isHideMsg) { - if (widget.index + 1 == widget.length) { - _isHideMsg = false; - Timer( - const Duration(milliseconds: 3500), - () { - setState(() { - _isHideMsg = true; - }); - }, - ); - } - } - } - - @override - void dispose() { - for (var element in _timerList) { - element.cancel(); - } - - super.dispose(); } @override @@ -170,7 +115,6 @@ class _FriendMessageBubbleState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (_isHideMsg) const Text('消息已隐藏'), - // text message content if (widget.text.isNotEmpty && !_isHideMsg) Text( @@ -180,58 +124,16 @@ class _FriendMessageBubbleState extends State { const SizedBox( height: 10, ), - // image content if have + // image content if has if (widget.attachments.isNotEmpty && !_isHideMsg) ...List.generate( widget.attachments.length, (int index) { - int attachmentItemIndex = - widget.attachmentItems.indexWhere( - (element) => - element.resource == - widget.attachments[index], - ); - int heroTagId = widget - .attachmentItems[attachmentItemIndex].id; - return Container( - margin: const EdgeInsets.only( - bottom: 15, - ), - constraints: const BoxConstraints( - maxHeight: 120, - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10.0), - ), - child: _isImagesLoaded[index] - ? GestureDetector( - onTap: () { - _onTapImage( - context, - widget.attachments[index], - ); - }, - child: Hero( - tag: heroTagId, - child: Image.file( - File( - '${getIt.get().baseImageDir}/${widget.attachments[index]}', - ), - ), - ), - ) - : Container( - width: 20, - height: 40, - padding: const EdgeInsets.symmetric( - vertical: 10, - ), - child: - const CircularProgressIndicator( - color: kSecondaryColor, - strokeWidth: 3.0, - ), - ), + String filename = widget.attachments[index]; + return AttachmentContainer( + isMyself: !isFriend, + filename: filename, + onTapImage: _onTapImage, ); }, ), @@ -261,15 +163,15 @@ class _FriendMessageBubbleState extends State { ); } - void _onTapImage(BuildContext context, String attachment) { - int attachmentIndex = widget.attachmentItems.indexWhere( - (element) => element.resource == attachment, + void _onTapImage(String attachment) { + int attachmentIndex = widget.allAttachments.indexWhere( + (element) => element == attachment, ); context.pushNamed( 'ImageView', extra: { - 'attachmentItems': widget.attachmentItems, + 'attachments': widget.allAttachments, 'index': attachmentIndex, }, ); diff --git a/lib/screens/message/components/message_input_box.dart b/lib/screens/message/components/message_input_box.dart index 18cd4d8..8bfd9f7 100755 --- a/lib/screens/message/components/message_input_box.dart +++ b/lib/screens/message/components/message_input_box.dart @@ -16,23 +16,9 @@ import 'package:together_mobile/models/user_model.dart'; import 'package:together_mobile/models/websocket_model.dart'; import 'package:together_mobile/utils/app_dir.dart'; import 'package:together_mobile/utils/format_datetime.dart'; +import 'package:together_mobile/utils/ws_receive_callback.dart'; import 'input_icon_button.dart'; -class SendMessage { - final int type; - final List receivers; - final List attachments; - final String dir; - - SendMessage(this.type, this.receivers, this.attachments, this.dir); - - SendMessage.fromJson(Map json) - : type = json['type'], - receivers = json['receivers'], - attachments = json['attachments'], - dir = json['dir']; -} - class MessageInputBox extends StatefulWidget { const MessageInputBox({ super.key, @@ -54,7 +40,7 @@ class _MessageInputBoxState extends State { final ImagePicker _picker = ImagePicker(); bool _hasMsg = false; - late Box _chatBox; + late Box _chatSettingBox; late Box _messageTBox; List _imageFileList = []; @@ -62,7 +48,7 @@ class _MessageInputBoxState extends State { @override void initState() { super.initState(); - _chatBox = Hive.box('chat_setting'); + _chatSettingBox = Hive.box('chat_setting'); _messageTBox = Hive.box('message_${widget.contactId}'); } @@ -231,20 +217,43 @@ class _MessageInputBoxState extends State { String text = _controller.text; String type = 'text/multipart'; List attachments = []; + Box apsBox = Hive.box('attachment_send'); + + var chatSetting = _chatSettingBox.get(widget.contactId); + if (chatSetting == null) { + _chatSettingBox.put( + widget.contactId, + ChatSetting( + widget.contactId, + widget.chatType, + false, + true, + false, + now, + 0, + ), + ); + } else { + chatSetting.latestDateTime = now; + chatSetting.unreadCount = 0; + chatSetting.isOpen = true; + _chatSettingBox.put(widget.contactId, chatSetting); + } + + String dirTime = formatDirTime(now); if (_imageFileList.isNotEmpty) { - String dirTime = formatDirTime(now); for (var i = 0; i < _imageFileList.length; i++) { - attachments.add('$dirTime/${getRandomFilename()}'); + String filename = '$dirTime/${getRandomFilename()}'; + int totalChunkNum = + ((await _imageFileList[i].length()) / chunkSize).ceil(); + attachments.add(filename); + apsBox.put( + filename, AttachmentProgress(0, totalChunkNum, 0, true, false)); } } - late int msgId; - if (_messageTBox.length == 0) { - msgId = 0; - } else { - msgId = _messageTBox.length - 1; - } + final msgId = formatMsgIDFromTime(now); _messageTBox.add( MessageT( @@ -258,6 +267,10 @@ class _MessageInputBoxState extends State { ), ); + Box msgIndexBox = + await Hive.openBox('msg_index_${widget.contactId}'); + msgIndexBox.put(msgId, _messageTBox.length - 1); + Future.delayed( const Duration(milliseconds: 50), () => widget.scrollController.animateTo( @@ -278,32 +291,31 @@ class _MessageInputBoxState extends State { }; if (widget.chatType == 0) { + if (attachments.isNotEmpty) { + String baseImageDir = getIt.get().baseImageDir; + for (var i = 0; i < attachments.length; i++) { + final dir = Directory('$baseImageDir/$dirTime'); + if (!(await dir.exists())) { + await dir.create(recursive: true); + } + await _imageFileList[i].saveTo('$baseImageDir/${attachments[i]}'); + } + } msg['event'] = 'friend-chat-msg'; msg['receiverId'] = widget.contactId; getIt.get().channel.sink.add(json.encode(msg)); - if (attachments.isNotEmpty) { - String baseImageDir = getIt.get().baseImageDir; - List encodedDatas = await compute( - bytes2json, - ( - 0, - [widget.contactId], - attachments, - _imageFileList, - baseImageDir, - ), - ); - - for (final data in encodedDatas) { - getIt.get().channel.sink.add(data); - } - } } else { - String baseImageDir = getIt.get().baseImageDir; List receiverIds = getIt .get() .groupChats[widget.contactId]! .members; + + if (attachments.isNotEmpty) { + String baseImageDir = getIt.get().baseImageDir; + for (var i = 0; i < attachments.length; i++) { + await _imageFileList[i].saveTo('$baseImageDir/${attachments[i]}'); + } + } receiverIds.remove(senderId); msg['event'] = 'group-chat-msg'; msg['groupChatId'] = widget.contactId; @@ -313,22 +325,6 @@ class _MessageInputBoxState extends State { getIt.get().groupChats[widget.contactId]!.remarkInGroupChat; msg['avatar'] = getIt.get().avatar; getIt.get().channel.sink.add(json.encode(msg)); - if (attachments.isNotEmpty) { - List encodedDatas = await compute( - bytes2json, - ( - 1, - receiverIds, - attachments, - _imageFileList, - baseImageDir, - ), - ); - - for (final data in encodedDatas) { - getIt.get().channel.sink.add(data); - } - } } _controller.text = ''; @@ -336,27 +332,6 @@ class _MessageInputBoxState extends State { _imageFileList = []; _hasMsg = false; }); - - var chatSetting = _chatBox.get(widget.contactId); - if (chatSetting == null) { - _chatBox.put( - widget.contactId, - ChatSetting( - widget.contactId, - widget.chatType, - false, - true, - false, - now, - 0, - ), - ); - } else { - chatSetting.latestDateTime = now; - chatSetting.unreadCount = 0; - chatSetting.isOpen = true; - _chatBox.put(widget.contactId, chatSetting); - } } bool _isShowTime(DateTime now) { @@ -372,44 +347,3 @@ class _MessageInputBoxState extends State { } } } - -Future> bytes2json( - ( - int, - List, - List, - List, - String, - ) args) async { - List encodedJson = []; - for (var i = 0; i < args.$3.length; i++) { - Uint8List bytes = await args.$4[i].readAsBytes(); - File file = File('${args.$5}/${args.$3[i]}'); - await file.create(recursive: true); - await file.writeAsBytes(bytes); - if (args.$1 == 0) { - encodedJson.add( - json.encode( - { - 'event': 'chat-image', - 'receiverId': args.$2[0], - 'filename': args.$3[i], - 'bytes': bytes, - }, - ), - ); - } else { - encodedJson.add( - json.encode( - { - 'event': 'chat-image', - 'receiverIds': args.$2, - 'filename': args.$3[i], - 'bytes': bytes, - }, - ), - ); - } - } - return encodedJson; -} diff --git a/lib/screens/message/friend_message_screen.dart b/lib/screens/message/friend_message_screen.dart index 98aae1c..eaec4cc 100755 --- a/lib/screens/message/friend_message_screen.dart +++ b/lib/screens/message/friend_message_screen.dart @@ -12,7 +12,6 @@ import 'package:together_mobile/models/route_state_model.dart'; import 'package:together_mobile/utils/format_datetime.dart'; import 'components/friend_message_bubble.dart'; import 'components/message_input_box.dart'; -import 'image_view_screen/image_view_screen.dart'; class FriendMessageScreen extends StatefulWidget { const FriendMessageScreen({ @@ -110,6 +109,7 @@ class _FriendMessageScreenState extends State { physics: const BouncingScrollPhysics( parent: AlwaysScrollableScrollPhysics(), ), + // addAutomaticKeepAlives: false, controller: _controller, shrinkWrap: true, reverse: true, @@ -123,24 +123,13 @@ class _FriendMessageScreenState extends State { MessageT messageT = messageTBox.getAt(i)!; List allAttachments = []; + // Do not reverse the list, cause what i want is + // rigth slide to next image, left to last otherwise for (var element in messageTBox.values) { if (element.attachments.isNotEmpty) { allAttachments.addAll(element.attachments); } } - // Do not reverse the list, cause what i want is - // rigth slide to next image, left to last otherwise - // allAttachments = List.from(allAttachments.reversed); - - List attachmentItems = List.generate( - allAttachments.length, - (int index) { - return AttachmentItem( - id: index, - resource: allAttachments[index], - ); - }, - ); return FriendMessageBubble( key: ValueKey(messageT.msgId), @@ -153,7 +142,7 @@ class _FriendMessageScreenState extends State { type: messageT.type, text: messageT.text, attachments: messageT.attachments, - attachmentItems: attachmentItems, + allAttachments: allAttachments, ); }, ); diff --git a/lib/screens/message/image_view_screen/image_view_screen.dart b/lib/screens/message/image_view_screen/image_view_screen.dart index e700a68..ad8ba67 100644 --- a/lib/screens/message/image_view_screen/image_view_screen.dart +++ b/lib/screens/message/image_view_screen/image_view_screen.dart @@ -27,7 +27,7 @@ class ImageViewScreen extends StatefulWidget { this.minScale, this.maxScale, this.initialIndex = 0, - required this.attachmentItems, + required this.attachments, this.scrollDirection = Axis.horizontal, }) : pageController = PageController(initialPage: initialIndex); @@ -36,7 +36,7 @@ class ImageViewScreen extends StatefulWidget { final dynamic maxScale; final int initialIndex; final PageController pageController; - final List attachmentItems; + final List attachments; final Axis scrollDirection; @override @@ -70,7 +70,7 @@ class _ImageViewScreenState extends State { PhotoViewGallery.builder( scrollPhysics: const BouncingScrollPhysics(), builder: _buildItem, - itemCount: widget.attachmentItems.length, + itemCount: widget.attachments.length, loadingBuilder: widget.loadingBuilder, backgroundDecoration: const BoxDecoration(color: Colors.black), pageController: widget.pageController, @@ -96,15 +96,15 @@ class _ImageViewScreenState extends State { } PhotoViewGalleryPageOptions _buildItem(BuildContext context, int index) { - final AttachmentItem item = widget.attachmentItems[index]; + final String item = widget.attachments[index]; return PhotoViewGalleryPageOptions( imageProvider: FileImage( - File('${getIt.get().baseImageDir}/${item.resource}'), + File('${getIt.get().baseImageDir}/$item'), ), initialScale: PhotoViewComputedScale.contained, - minScale: PhotoViewComputedScale.contained * (0.5 + index / 10), + minScale: PhotoViewComputedScale.contained * 0.5, maxScale: PhotoViewComputedScale.covered * 4.1, - heroAttributes: PhotoViewHeroAttributes(tag: item.id), + // heroAttributes: PhotoViewHeroAttributes(tag: widget.heroTag), ); } } diff --git a/lib/use_animation.dart b/lib/use_animation.dart new file mode 100644 index 0000000..c70fbbf --- /dev/null +++ b/lib/use_animation.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp(const UseAnimation()); +} + +class UseAnimation extends StatefulWidget { + const UseAnimation({super.key}); + + @override + State createState() => _UseAnimationState(); +} + +class _UseAnimationState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _tween; + + @override + void initState() { + _controller = AnimationController(vsync: this); + _tween = Tween(begin: 0, end: 1).animate(_controller) + ..addStatusListener((status) { + // print(status); + }) + ..addListener(() { + setState(() {}); + }); + // _controller.forward(); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + _controller.dispose(); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Use Animation', + home: Scaffold( + appBar: AppBar(title: const Text('use animation')), + body: Center( + child: SizedBox( + height: 300, + width: 200, + child: Stack( + alignment: Alignment.center, + children: [ + Image.asset('assets/images/user_2.png'), + Positioned.fill( + child: Container( + color: _tween.value == 1.0 + ? null + : const Color.fromARGB(255, 36, 36, 36) + .withOpacity(0.3), + child: Center( + child: Text( + (_tween.value * 100).toStringAsFixed(1), + style: const TextStyle( + color: Colors.green, + fontSize: 24, + ), + ), + ), + ), + ), + CircularProgressIndicator( + value: _tween.value, + ), + ], + ), + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + if (_tween.value == 1.0) { + _controller.reset(); + } + print(_tween.value); + // print(_controller.value); + _controller.animateTo(_tween.value + 0.1, + duration: const Duration(milliseconds: 200)); + print(_tween.value); + }, + child: const Icon(Icons.plus_one), + ), + ), + ); + } +} diff --git a/lib/utils/format_datetime.dart b/lib/utils/format_datetime.dart index 46844cc..5853801 100644 --- a/lib/utils/format_datetime.dart +++ b/lib/utils/format_datetime.dart @@ -94,6 +94,8 @@ String formatMsgIDFromTime(DateTime dateTime) { dateTime.month < 10 ? '0${dateTime.month}' : '${dateTime.month}'; String day = dateTime.day < 10 ? '0${dateTime.day}' : '${dateTime.day}'; String hour = dateTime.hour < 10 ? '0${dateTime.hour}' : '${dateTime.hour}'; + String second = + dateTime.second < 10 ? '0${dateTime.second}' : '${dateTime.second}'; String minute = dateTime.minute < 10 ? '0${dateTime.minute}' : '${dateTime.minute}'; String millisecond = dateTime.millisecond >= 100 @@ -102,5 +104,5 @@ String formatMsgIDFromTime(DateTime dateTime) { ? '0${dateTime.minute}' : '00${dateTime.minute}'; - return '$year$month$day$hour$minute$millisecond'; + return '$year$month$day$hour$minute$second$millisecond'; } diff --git a/lib/utils/ws_receive_callback.dart b/lib/utils/ws_receive_callback.dart new file mode 100644 index 0000000..d6a4b20 --- /dev/null +++ b/lib/utils/ws_receive_callback.dart @@ -0,0 +1,449 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:hive/hive.dart'; +import 'package:together_mobile/database/box_type.dart'; +import 'package:together_mobile/database/hive_database.dart'; +import 'package:together_mobile/models/apply_list_model.dart'; +import 'package:together_mobile/models/contact_model.dart'; +import 'package:together_mobile/models/init_get_it.dart'; +import 'package:together_mobile/models/route_state_model.dart'; +import 'package:together_mobile/models/user_model.dart'; +import 'package:together_mobile/models/websocket_model.dart'; +import 'package:together_mobile/notification_api.dart'; +import 'package:together_mobile/request/message.dart'; + +const int chunkSize = 1024 * 1024 * 2; + +Future receiveFriendMsg( + Map msg, + bool isShowNotification, +) async { + print('=================收到了好友信息事件=================='); + print(msg); + print('======================================='); + String senderId = msg['senderId'] as String; + Box chatSettingBox = Hive.box('chat_setting'); + Box messageTBox = await HiveDatabase.openMessageBox(senderId); + Box msgIndexBox = await Hive.openBox('msg_index_$senderId'); + ChatSetting? chatSetting = chatSettingBox.get(senderId); + DateTime dateTime = DateTime.parse(msg['dateTime'] as String); + + if (chatSetting == null) { + chatSettingBox.put( + senderId, + ChatSetting( + senderId, + 0, + false, + true, + false, + dateTime, + 1, + ), + ); + } else if (msgIndexBox.get(msg['msgId'] as String) != null) { + return; + } else { + chatSetting.isOpen = true; + chatSetting.latestDateTime = dateTime; + chatSetting.unreadCount++; + chatSettingBox.put(senderId, chatSetting); + } + + List attachments = List.from(msg['attachments']); + final DateTime now = DateTime.parse(msg['dateTime'] as String); + + for (var attachment in attachments) { + Box apr = + Hive.box('attachment_receive'); + apr.put(attachment, AttachmentProgress(0, 1000, 0, true, false)); + } + + messageTBox.add( + MessageT( + msg['msgId'] as String, + senderId, + msg['text'], + msg['type'], + now, + msg['isShowTime'], + attachments, + ), + ); + + if (!isShowNotification) { + return; + } + + String name = getIt.get().friends[senderId]!.friendRemark.isEmpty + ? getIt.get().friends[senderId]!.nickname + : getIt.get().friends[senderId]!.friendRemark; + + String routeName = getIt.get().currentPathName; + + String avatar = getIt.get().friends[senderId]!.avatar; + + if (!getIt.get().isVisible) { + NotificationAPI.showMessageNotifications( + senderId: senderId, + name: name, + avatar: avatar, + text: msg['text'] as String, + ); + } else if (routeName == 'Message') { + if (!getIt.get().query.containsValue(senderId)) { + NotificationAPI.showMessageNotifications( + senderId: senderId, + name: name, + avatar: avatar, + text: msg['text'] as String, + ); + } + } else if (routeName != 'Chat') { + NotificationAPI.showMessageNotifications( + senderId: senderId, + name: name, + avatar: avatar, + text: msg['text'] as String, + ); + } +} + +void receiveApplyFriend(Map msg) { + print('=================收到了申请好友事件=================='); + print(msg); + print('======================================='); + getIt.get().addJson(msg); +} + +void receiveFriendAdded(Map msg) { + print('=================收到了申请好友通过事件=================='); + print(msg); + print('======================================='); + getIt.get().addFriend(msg['friendId'], msg['setting']); + getIt + .get() + .addFriendAccountProfile(msg['friendId'], msg['accountProfile']); +} + +void receiveFriendDeleted(Map msg) { + print('=================收到了解除好友事件=================='); + print(msg); + print('======================================='); + getIt.get().removeFriend(msg['friendId']); + getIt.get().removeFriend(msg['friendId']); +} + +void receiveGroupChatCreation(Map msg) { + print('=================收到了群聊邀请事件=================='); + print(msg); + print('======================================='); + String groupChatId = msg['groupChat']['id']; + getIt.get().addGroupChat(groupChatId); + getIt.get().addGroupChatProfile(groupChatId, msg); +} + +void receiveGroupChatMsg( + Map msg, + bool isShowNotification, +) async { + print('=================收到了群聊信息事件=================='); + print(msg); + print('======================================='); + String senderId = msg['senderId'] as String; + String groupChatId = msg['groupChatId'] as String; + Box chatSettingBox = Hive.box('chat_setting'); + Box messageTBox = await HiveDatabase.openMessageBox(groupChatId); + Box msgIndexBox = await Hive.openBox('msg_index_$groupChatId'); + ChatSetting? chatSetting = chatSettingBox.get(groupChatId); + DateTime dateTime = DateTime.parse(msg['dateTime'] as String); + + getIt.get().addGroupChatMemberProfile( + groupChatId, + msg['senderId'], + { + 'avatar': msg['avatar'], + 'nickname': msg['nickname'], + 'remarkInGroupChat': msg['remarkInGroupChat'], + }, + ); + + if (chatSetting == null) { + chatSettingBox.put( + groupChatId, + ChatSetting( + groupChatId, + 1, + false, + true, + false, + dateTime, + 1, + ), + ); + } else if (msgIndexBox.get(msg['msgId'] as String) != null) { + return; + } else { + chatSetting.isOpen = true; + chatSetting.latestDateTime = dateTime; + chatSetting.unreadCount++; + chatSettingBox.put(groupChatId, chatSetting); + } + + List attachments = List.from(msg['attachments']); + final DateTime now = DateTime.parse(msg['dateTime'] as String); + + messageTBox.add( + MessageT( + msg['msgId'] as String, + senderId, + msg['text'], + msg['type'], + now, + msg['isShowTime'], + attachments, + ), + ); + + if (!isShowNotification) { + return; + } + + String avatar = + getIt.get().groupChats[groupChatId]!.avatar; + late String name; + + if (getIt.get().friends.containsKey(senderId)) { + if (getIt.get().friends[senderId]!.friendRemark.isNotEmpty) { + name = getIt.get().friends[senderId]!.friendRemark; + } else if ((msg['remarkInGroupChat'] as String).isNotEmpty) { + name = msg['remarkInGroupChat']; + } else { + name = msg['nickname']; + } + } else { + name = (msg['remarkInGroupChat'] as String).isNotEmpty + ? msg['remarkInGroupChat'] + : msg['nickname']; + } + + String routeName = getIt.get().currentPathName; + + if (!getIt.get().isVisible) { + NotificationAPI.showMessageNotifications( + senderId: senderId, + name: name, + avatar: avatar, + text: msg['text'] as String, + groupChatId: groupChatId, + ); + } else if (routeName == 'Message') { + if (!getIt.get().query.containsValue(senderId)) { + NotificationAPI.showMessageNotifications( + senderId: senderId, + name: name, + avatar: avatar, + text: msg['text'] as String, + groupChatId: groupChatId, + ); + } + } else if (routeName != 'Chat') { + NotificationAPI.showMessageNotifications( + senderId: senderId, + name: name, + avatar: avatar, + text: msg['text'] as String, + groupChatId: groupChatId, + ); + } +} + +void receiveChatImages(Map msg) async { + print('=================收到了聊天图片事件=================='); + // print(msg); + print('======================================='); + String chatImageDir = getIt.get().baseImageDir; + String filename = msg['filename']; + String filePath = '$chatImageDir/$filename'; + File file = File(filePath); + + if (await file.exists()) { + return; + } + + late Box aprBox; + try { + aprBox = Hive.box('attachment_receive'); + } catch (_) { + aprBox = await Hive.openBox('attachment_receive'); + } + String dirTime = filename.split('/')[0]; + Directory dir = Directory('$chatImageDir/$dirTime'); + + if (!(await dir.exists())) { + await dir.create(recursive: true); + } + + int totalChunkNum = msg['totalChunkNum']; + + if (totalChunkNum == 1) { + await file.writeAsBytes(List.from(msg['bytes'])); + Future.delayed( + const Duration(milliseconds: 200), + () { + aprBox.put( + filename, + AttachmentProgress(1, 1, 1.0, true, false), + ); + }, + ); + return; + } + + File tempFile = File('$chatImageDir/${msg['tempFilename']}'); + AttachmentProgress? ap = aprBox.get(filename); + + if (!(await tempFile.exists())) { + await tempFile.writeAsBytes(List.from(msg['bytes'])); + } + + if (ap == null) { + aprBox.put( + filename, + AttachmentProgress( + 1, + totalChunkNum, + 1 / totalChunkNum, + true, + false, + ), + ); + } else { + ap.hasChunkNum += 1; + ap.progress = ap.hasChunkNum / totalChunkNum; + ap.isValid = true; + + List tempFiles = List.generate( + totalChunkNum, + (index) { + String tempFilePath = + '$chatImageDir/temp/$filename-$totalChunkNum-$index'; + return File(tempFilePath); + }, + ); + + // assemble chunks when all the chunks are arrived + if (tempFiles.every((element) => element.existsSync())) { + final openedFile = file.openWrite(mode: FileMode.append); + + for (var i = 0; i < totalChunkNum; i++) { + Uint8List bytes = await tempFiles[i].readAsBytes(); + openedFile.add(bytes); + await tempFiles[i].delete(); + } + openedFile.close(); + ap.hasChunkNum = totalChunkNum; + ap.progress = 1.0; + ap.isValid = true; + } + + aprBox.put(filename, ap); + } +} + +Future receivePullChatImage(Map msg) async { + print('=================收到了拉取图片请求=================='); + print(msg); + print('======================================='); + String baseImageDir = getIt.get().baseImageDir; + Map sentMsg = { + 'event': 'chat-image', + 'senderId': getIt.get().id, + }; + + if (msg['chatType'] == 0) { + sentMsg['receiverId'] = msg['receiverId']; + } else { + sentMsg['receiverIds'] = msg['receiverIds']; + } + + for (var filename in msg['attachments']) { + File file = File('$baseImageDir/$filename'); + int fileSize = await file.length(); + int totalChunkNum = (fileSize / chunkSize).ceil(); + int start = 0; + int end = fileSize >= chunkSize ? start + chunkSize : start + fileSize; + List bytes = []; + await file.openRead(start, end).forEach((element) => bytes.addAll(element)); + + sentMsg['filename'] = filename; + sentMsg['totalChunkNum'] = totalChunkNum; + sentMsg['tempFilename'] = 'temp/$filename-$totalChunkNum-0'; + sentMsg['currentChunkNum'] = 0; + sentMsg['bytes'] = bytes; + + await uploadChatAttachment(sentMsg); + getIt.get().addSendImageTimer(filename, totalChunkNum); + } +} + +Future receiveCheckChatImage(Map msg) async { + print('=================收到了服务端确认收到图片事件=================='); + // print(msg); + print('======================================='); + int nextChunkNum = msg['currentChunkNum'] + 1; + int totalChunkNum = msg['totalChunkNum']; + String baseImageDir = getIt.get().baseImageDir; + String filename = msg['filename']; + Box asBox = + Hive.box('attachment_send'); + AttachmentProgress aps = asBox.get(filename)!; + + getIt.get().removeSendImageTimer(filename); + aps.hasChunkNum += 1; + aps.progress = aps.hasChunkNum / aps.totalChunkNum; + asBox.put(filename, aps); + // print(aps.hasChunkNum); + // print(aps.totalChunkNum); + // print('已发送了: ${aps.progress}'); + if (nextChunkNum == totalChunkNum) { + return; + } + + // Start to send next chunk + int start = nextChunkNum * chunkSize; + File file = File('$baseImageDir/$filename'); + List bytes = []; + + if (nextChunkNum + 1 == totalChunkNum) { + await file + .openRead(start) + .forEach((element) => bytes.addAll(element as List)); + } else { + await file + .openRead(start, start + chunkSize) + .forEach((element) => bytes.addAll(element as List)); + } + + Map sentMsg = { + 'event': 'chat-image', + 'senderId': getIt.get().id, + }; + + if (msg['chatType'] == 0) { + sentMsg['receiverId'] = msg['receiverId']; + } else { + sentMsg['receiverIds'] = msg['receiverIds']; + } + + sentMsg['filename'] = filename; + sentMsg['totalChunkNum'] = totalChunkNum; + sentMsg['tempFilename'] = 'temp/$filename-$totalChunkNum-$nextChunkNum'; + sentMsg['currentChunkNum'] = nextChunkNum; + sentMsg['bytes'] = bytes; + + uploadChatAttachment(sentMsg); + getIt.get().addSendImageTimer(filename, totalChunkNum); +} diff --git a/pubspec.lock b/pubspec.lock index 6233a3c..176adf0 100755 --- a/pubspec.lock +++ b/pubspec.lock @@ -1157,14 +1157,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" sqflite: dependency: transitive description: @@ -1265,10 +1257,10 @@ packages: dependency: transitive description: name: uuid - sha256: e03928880bdbcbf496fb415573f5ab7b1ea99b9b04f669c01104d085893c3134 + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "3.0.7" vector_math: dependency: transitive description: @@ -1357,6 +1349,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + worker_manager: + dependency: "direct main" + description: + name: worker_manager + sha256: caab0544cb95471e8d1417d21e25838ee4f614f4651a9e8e5a4f26bfeff5f312 + url: "https://pub.dev" + source: hosted + version: "6.3.1" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c8ccce7..a870c68 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -61,6 +61,7 @@ dependencies: timezone: ^0.9.2 video_player: ^2.6.1 web_socket_channel: ^2.4.0 + worker_manager: ^6.3.1 dev_dependencies: build_runner: ^2.4.5