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<MessageT> {
       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<MessageT> {
           runtimeType == other.runtimeType &&
           typeId == other.typeId;
 }
+
+class AttachmentProgressAdapter extends TypeAdapter<AttachmentProgress> {
+  @override
+  final int typeId = 2;
+
+  @override
+  AttachmentProgress read(BinaryReader reader) {
+    final numOfFields = reader.readByte();
+    final fields = <int, dynamic>{
+      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<int>('msg_index_${chatBox.contactId}');
     }
 
+    await Hive.openBox<AttachmentProgress>('attachment_send');
+    await Hive.openBox<AttachmentProgress>('attachment_receive');
+
     _isInitialised = true;
   }
 
   static void registerAdapter() {
     Hive.registerAdapter(ChatSettingAdapter());
     Hive.registerAdapter(MessageTAdapter());
+    Hive.registerAdapter(AttachmentProgressAdapter());
   }
 
   static Future<Box<MessageT>> 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<String, Timer> 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<String, dynamic> data = json.decode(jsonData);
+    // Map<String, dynamic> data = json.decode(jsonData);
+    Map<String, dynamic> 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<String, dynamic> msg, bool isShowNotification) async {
-  print('=================收到了好友信息事件==================');
-  print(msg);
-  print('=======================================');
-  String senderId = msg['senderId'] as String;
-  Box<ChatSetting> chatSettingBox = Hive.box<ChatSetting>('chat_setting');
-  Box<MessageT> 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<String> 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<Contact>().friends[senderId]!.friendRemark.isEmpty
-      ? getIt.get<ContactAccountProfile>().friends[senderId]!.nickname
-      : getIt.get<Contact>().friends[senderId]!.friendRemark;
-
-  String routeName = getIt.get<RouteState>().currentPathName;
-
-  String avatar = getIt.get<ContactAccountProfile>().friends[senderId]!.avatar;
-
-  if (!getIt.get<RouteState>().isVisible) {
-    NotificationAPI.showMessageNotifications(
-      senderId: senderId,
-      name: name,
-      avatar: avatar,
-      text: msg['text'] as String,
-    );
-  } else if (routeName == 'Message') {
-    if (!getIt.get<RouteState>().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<AttachmentProgress> attachmentLoadingBox;
+        try {
+          attachmentLoadingBox =
+              Hive.box<AttachmentProgress>('attachment_receive');
+        } catch (_) {
+          attachmentLoadingBox =
+              await Hive.openBox<AttachmentProgress>('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<String, dynamic> msg) {
-  print('=================收到了申请好友事件==================');
-  print(msg);
-  print('=======================================');
-  getIt.get<ApplyList>().addJson(msg);
-}
-
-void receiveFriendAdded(Map<String, dynamic> msg) {
-  print('=================收到了申请好友通过事件==================');
-  print(msg);
-  print('=======================================');
-  getIt.get<Contact>().addFriend(msg['friendId'], msg['setting']);
-  getIt
-      .get<ContactAccountProfile>()
-      .addFriendAccountProfile(msg['friendId'], msg['accountProfile']);
-}
-
-void receiveFriendDeleted(Map<String, dynamic> msg) {
-  print('=================收到了解除好友事件==================');
-  print(msg);
-  print('=======================================');
-  getIt.get<Contact>().removeFriend(msg['friendId']);
-  getIt.get<ContactAccountProfile>().removeFriend(msg['friendId']);
-}
-
-void receiveChatImages(Map<String, dynamic> msg) async {
-  print('=================收到了聊天图片事件==================');
-  print(msg);
-  print('=======================================');
-  String chatImageDir = getIt.get<UserProfile>().baseImageDir;
-  File file = File('$chatImageDir/${msg['filename']}');
-  if (await file.exists()) {
-    return;
-  } else {
-    await file.create(recursive: true);
-    await file.writeAsBytes(List<int>.from(msg['bytes']));
-  }
-}
-
-void receiveGroupChatCreation(Map<String, dynamic> msg) {
-  print('=================收到了群聊邀请事件==================');
-  print(msg);
-  print('=======================================');
-  String groupChatId = msg['groupChat']['id'];
-  getIt.get<Contact>().addGroupChat(groupChatId);
-  getIt.get<ContactAccountProfile>().addGroupChatProfile(groupChatId, msg);
-}
-
-void receiveGroupChatMsg(
-    Map<String, dynamic> msg, bool isShowNotification) async {
-  print('=================收到了群聊信息事件==================');
-  print(msg);
-  print('=======================================');
-  String senderId = msg['senderId'] as String;
-  String groupChatId = msg['groupChatId'] as String;
-  Box<ChatSetting> chatSettingBox = Hive.box<ChatSetting>('chat_setting');
-  Box<MessageT> messageTBox = await HiveDatabase.openMessageBox(groupChatId);
-  ChatSetting? chatSetting = chatSettingBox.get(groupChatId);
-  DateTime dateTime = DateTime.parse(msg['dateTime'] as String);
-
-  getIt.get<ContactAccountProfile>().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<String> 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<ContactAccountProfile>().groupChats[groupChatId]!.avatar;
-  late String name;
-
-  if (getIt.get<Contact>().friends.containsKey(senderId)) {
-    if (getIt.get<Contact>().friends[senderId]!.friendRemark.isNotEmpty) {
-      name = getIt.get<Contact>().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<RouteState>().currentPathName;
-
-  if (!getIt.get<RouteState>().isVisible) {
-    NotificationAPI.showMessageNotifications(
-      senderId: senderId,
-      name: name,
-      avatar: avatar,
-      text: msg['text'] as String,
-      groupChatId: groupChatId,
-    );
-  } else if (routeName == 'Message') {
-    if (!getIt.get<RouteState>().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<Map<String, dynamic>> getUnreceivedMsg(String userId) async {
 
   return response.data;
 }
+
+Future<Map<String, dynamic>> uploadChatAttachment(
+  Map<String, dynamic> 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<String, Object> extra = state.extra as Map<String, Object>;
-            for (var a in extra['attachmentItems']! as List<AttachmentItem>) {
-              print(a.id);
-            }
             return ImageViewScreen(
-              attachmentItems:
-                  extra['attachmentItems']! as List<AttachmentItem>,
+              attachments:
+                  extra['attachments']! as List<String>,
               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<AttachmentContainer> createState() => _AttachmentContainerState();
+}
+
+class _AttachmentContainerState extends State<AttachmentContainer>
+    with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
+  late AnimationController _controller;
+  late Animation<double> _animation;
+
+  @override
+  bool get wantKeepAlive => true;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = AnimationController(vsync: this);
+    _animation = Tween<double>(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<AttachmentProgress>('attachment_send')
+                .listenable(keys: [widget.filename])
+            : Hive.box<AttachmentProgress>('attachment_receive')
+                .listenable(keys: [widget.filename]),
+        builder: (context, apBox, _) {
+          double progress = apBox.get(widget.filename)!.progress;
+          String filePath =
+              '${getIt.get<UserProfile>().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<String> attachments;
-  final List<AttachmentItem> attachmentItems;
+  final List<String> attachments, allAttachments;
 
   @override
   State<FriendMessageBubble> createState() => _FriendMessageBubbleState();
@@ -41,65 +37,14 @@ class FriendMessageBubble extends StatefulWidget {
 
 class _FriendMessageBubbleState extends State<FriendMessageBubble> {
   // add late here so you can access widget instance
-  List<bool> _isImagesLoaded = [];
-  final List<Timer> _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<UserProfile>().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<ChatSetting>('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<FriendMessageBubble> {
                           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<FriendMessageBubble> {
                             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<UserProfile>().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<FriendMessageBubble> {
     );
   }
 
-  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<String> receivers;
-  final List<String> attachments;
-  final String dir;
-
-  SendMessage(this.type, this.receivers, this.attachments, this.dir);
-
-  SendMessage.fromJson(Map<String, dynamic> 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<MessageInputBox> {
   final ImagePicker _picker = ImagePicker();
   bool _hasMsg = false;
 
-  late Box<ChatSetting> _chatBox;
+  late Box<ChatSetting> _chatSettingBox;
   late Box<MessageT> _messageTBox;
 
   List<XFile> _imageFileList = [];
@@ -62,7 +48,7 @@ class _MessageInputBoxState extends State<MessageInputBox> {
   @override
   void initState() {
     super.initState();
-    _chatBox = Hive.box<ChatSetting>('chat_setting');
+    _chatSettingBox = Hive.box<ChatSetting>('chat_setting');
     _messageTBox = Hive.box<MessageT>('message_${widget.contactId}');
   }
 
@@ -231,20 +217,43 @@ class _MessageInputBoxState extends State<MessageInputBox> {
     String text = _controller.text;
     String type = 'text/multipart';
     List<String> attachments = [];
+    Box<AttachmentProgress> 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<MessageInputBox> {
       ),
     );
 
+    Box<int> msgIndexBox =
+        await Hive.openBox<int>('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<MessageInputBox> {
     };
 
     if (widget.chatType == 0) {
+      if (attachments.isNotEmpty) {
+        String baseImageDir = getIt.get<UserProfile>().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<WebSocketManager>().channel.sink.add(json.encode(msg));
-      if (attachments.isNotEmpty) {
-        String baseImageDir = getIt.get<UserProfile>().baseImageDir;
-        List<String> encodedDatas = await compute(
-          bytes2json,
-          (
-            0,
-            [widget.contactId],
-            attachments,
-            _imageFileList,
-            baseImageDir,
-          ),
-        );
-
-        for (final data in encodedDatas) {
-          getIt.get<WebSocketManager>().channel.sink.add(data);
-        }
-      }
     } else {
-      String baseImageDir = getIt.get<UserProfile>().baseImageDir;
       List<String> receiverIds = getIt
           .get<ContactAccountProfile>()
           .groupChats[widget.contactId]!
           .members;
+
+      if (attachments.isNotEmpty) {
+        String baseImageDir = getIt.get<UserProfile>().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<MessageInputBox> {
           getIt.get<Contact>().groupChats[widget.contactId]!.remarkInGroupChat;
       msg['avatar'] = getIt.get<UserProfile>().avatar;
       getIt.get<WebSocketManager>().channel.sink.add(json.encode(msg));
-      if (attachments.isNotEmpty) {
-        List<String> encodedDatas = await compute(
-          bytes2json,
-          (
-            1,
-            receiverIds,
-            attachments,
-            _imageFileList,
-            baseImageDir,
-          ),
-        );
-
-        for (final data in encodedDatas) {
-          getIt.get<WebSocketManager>().channel.sink.add(data);
-        }
-      }
     }
 
     _controller.text = '';
@@ -336,27 +332,6 @@ class _MessageInputBoxState extends State<MessageInputBox> {
       _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<MessageInputBox> {
     }
   }
 }
-
-Future<List<String>> bytes2json(
-    (
-      int,
-      List<String>,
-      List<String>,
-      List<XFile>,
-      String,
-    ) args) async {
-  List<String> 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<FriendMessageScreen> {
                     physics: const BouncingScrollPhysics(
                       parent: AlwaysScrollableScrollPhysics(),
                     ),
+                    // addAutomaticKeepAlives: false,
                     controller: _controller,
                     shrinkWrap: true,
                     reverse: true,
@@ -123,24 +123,13 @@ class _FriendMessageScreenState extends State<FriendMessageScreen> {
                       MessageT messageT = messageTBox.getAt(i)!;
 
                       List<String> 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<AttachmentItem> 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<FriendMessageScreen> {
                         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<AttachmentItem> attachmentItems;
+  final List<String> attachments;
   final Axis scrollDirection;
 
   @override
@@ -70,7 +70,7 @@ class _ImageViewScreenState extends State<ImageViewScreen> {
               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<ImageViewScreen> {
   }
 
   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<UserProfile>().baseImageDir}/${item.resource}'),
+        File('${getIt.get<UserProfile>().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<UseAnimation> createState() => _UseAnimationState();
+}
+
+class _UseAnimationState extends State<UseAnimation>
+    with SingleTickerProviderStateMixin {
+  late AnimationController _controller;
+  late Animation<double> _tween;
+
+  @override
+  void initState() {
+    _controller = AnimationController(vsync: this);
+    _tween = Tween<double>(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<void> receiveFriendMsg(
+  Map<String, dynamic> msg,
+  bool isShowNotification,
+) async {
+  print('=================收到了好友信息事件==================');
+  print(msg);
+  print('=======================================');
+  String senderId = msg['senderId'] as String;
+  Box<ChatSetting> chatSettingBox = Hive.box<ChatSetting>('chat_setting');
+  Box<MessageT> messageTBox = await HiveDatabase.openMessageBox(senderId);
+  Box<int> msgIndexBox = await Hive.openBox<int>('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<String> attachments = List.from(msg['attachments']);
+  final DateTime now = DateTime.parse(msg['dateTime'] as String);
+
+  for (var attachment in attachments) {
+    Box<AttachmentProgress> apr =
+        Hive.box<AttachmentProgress>('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<Contact>().friends[senderId]!.friendRemark.isEmpty
+      ? getIt.get<ContactAccountProfile>().friends[senderId]!.nickname
+      : getIt.get<Contact>().friends[senderId]!.friendRemark;
+
+  String routeName = getIt.get<RouteState>().currentPathName;
+
+  String avatar = getIt.get<ContactAccountProfile>().friends[senderId]!.avatar;
+
+  if (!getIt.get<RouteState>().isVisible) {
+    NotificationAPI.showMessageNotifications(
+      senderId: senderId,
+      name: name,
+      avatar: avatar,
+      text: msg['text'] as String,
+    );
+  } else if (routeName == 'Message') {
+    if (!getIt.get<RouteState>().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<String, dynamic> msg) {
+  print('=================收到了申请好友事件==================');
+  print(msg);
+  print('=======================================');
+  getIt.get<ApplyList>().addJson(msg);
+}
+
+void receiveFriendAdded(Map<String, dynamic> msg) {
+  print('=================收到了申请好友通过事件==================');
+  print(msg);
+  print('=======================================');
+  getIt.get<Contact>().addFriend(msg['friendId'], msg['setting']);
+  getIt
+      .get<ContactAccountProfile>()
+      .addFriendAccountProfile(msg['friendId'], msg['accountProfile']);
+}
+
+void receiveFriendDeleted(Map<String, dynamic> msg) {
+  print('=================收到了解除好友事件==================');
+  print(msg);
+  print('=======================================');
+  getIt.get<Contact>().removeFriend(msg['friendId']);
+  getIt.get<ContactAccountProfile>().removeFriend(msg['friendId']);
+}
+
+void receiveGroupChatCreation(Map<String, dynamic> msg) {
+  print('=================收到了群聊邀请事件==================');
+  print(msg);
+  print('=======================================');
+  String groupChatId = msg['groupChat']['id'];
+  getIt.get<Contact>().addGroupChat(groupChatId);
+  getIt.get<ContactAccountProfile>().addGroupChatProfile(groupChatId, msg);
+}
+
+void receiveGroupChatMsg(
+  Map<String, dynamic> msg,
+  bool isShowNotification,
+) async {
+  print('=================收到了群聊信息事件==================');
+  print(msg);
+  print('=======================================');
+  String senderId = msg['senderId'] as String;
+  String groupChatId = msg['groupChatId'] as String;
+  Box<ChatSetting> chatSettingBox = Hive.box<ChatSetting>('chat_setting');
+  Box<MessageT> messageTBox = await HiveDatabase.openMessageBox(groupChatId);
+  Box<int> msgIndexBox = await Hive.openBox('msg_index_$groupChatId');
+  ChatSetting? chatSetting = chatSettingBox.get(groupChatId);
+  DateTime dateTime = DateTime.parse(msg['dateTime'] as String);
+
+  getIt.get<ContactAccountProfile>().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<String> 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<ContactAccountProfile>().groupChats[groupChatId]!.avatar;
+  late String name;
+
+  if (getIt.get<Contact>().friends.containsKey(senderId)) {
+    if (getIt.get<Contact>().friends[senderId]!.friendRemark.isNotEmpty) {
+      name = getIt.get<Contact>().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<RouteState>().currentPathName;
+
+  if (!getIt.get<RouteState>().isVisible) {
+    NotificationAPI.showMessageNotifications(
+      senderId: senderId,
+      name: name,
+      avatar: avatar,
+      text: msg['text'] as String,
+      groupChatId: groupChatId,
+    );
+  } else if (routeName == 'Message') {
+    if (!getIt.get<RouteState>().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<String, dynamic> msg) async {
+  print('=================收到了聊天图片事件==================');
+  // print(msg);
+  print('=======================================');
+  String chatImageDir = getIt.get<UserProfile>().baseImageDir;
+  String filename = msg['filename'];
+  String filePath = '$chatImageDir/$filename';
+  File file = File(filePath);
+
+  if (await file.exists()) {
+    return;
+  }
+
+  late Box<AttachmentProgress> aprBox;
+  try {
+    aprBox = Hive.box<AttachmentProgress>('attachment_receive');
+  } catch (_) {
+    aprBox = await Hive.openBox<AttachmentProgress>('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<int>.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<int>.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<File> 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<void> receivePullChatImage(Map<String, dynamic> msg) async {
+  print('=================收到了拉取图片请求==================');
+  print(msg);
+  print('=======================================');
+  String baseImageDir = getIt.get<UserProfile>().baseImageDir;
+  Map<String, dynamic> sentMsg = {
+    'event': 'chat-image',
+    'senderId': getIt.get<UserAccount>().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<int> 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<WebSocketManager>().addSendImageTimer(filename, totalChunkNum);
+  }
+}
+
+Future<void> receiveCheckChatImage(Map<String, dynamic> msg) async {
+  print('=================收到了服务端确认收到图片事件==================');
+  // print(msg);
+  print('=======================================');
+  int nextChunkNum = msg['currentChunkNum'] + 1;
+  int totalChunkNum = msg['totalChunkNum'];
+  String baseImageDir = getIt.get<UserProfile>().baseImageDir;
+  String filename = msg['filename'];
+  Box<AttachmentProgress> asBox =
+      Hive.box<AttachmentProgress>('attachment_send');
+  AttachmentProgress aps = asBox.get(filename)!;
+
+  getIt.get<WebSocketManager>().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<int> bytes = [];
+
+  if (nextChunkNum + 1 == totalChunkNum) {
+    await file
+        .openRead(start)
+        .forEach((element) => bytes.addAll(element as List<int>));
+  } else {
+    await file
+        .openRead(start, start + chunkSize)
+        .forEach((element) => bytes.addAll(element as List<int>));
+  }
+
+  Map<String, dynamic> sentMsg = {
+    'event': 'chat-image',
+    'senderId': getIt.get<UserAccount>().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<WebSocketManager>().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