zwp před 5 měsíci
rodič
revize
c16c010d37

+ 4 - 0
AIIM.xcodeproj/project.pbxproj

@@ -220,9 +220,13 @@
 			);
 			inputFileListPaths = (
 			);
+			inputPaths = (
+			);
 			name = "[CP] Embed Pods Frameworks";
 			outputFileListPaths = (
 			);
+			outputPaths = (
+			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AIIM/Pods-AIIM-frameworks.sh\"\n";

+ 21 - 0
AIIM/Assets.xcassets/icon_resend.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "icon_resend.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

binární
AIIM/Assets.xcassets/icon_resend.imageset/icon_resend.png


+ 21 - 0
AIIM/Assets.xcassets/icon_revert.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "icon_revert.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

binární
AIIM/Assets.xcassets/icon_revert.imageset/icon_revert.png


+ 54 - 0
AIIM/Common/Manager/MessageStatusManager.h

@@ -0,0 +1,54 @@
+//
+//  MessageStatusManager.h
+//  AIIM
+//
+//  Created by AI Assistant on 2025/10/21.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// 消息状态管理器 - 使用 plist 文件存储消息状态
+@interface MessageStatusManager : NSObject
+
+/// 单例
++ (instancetype)sharedInstance;
+
+/// 保存消息状态
+/// @param messageId 消息ID
+/// @param status 消息状态 (0=发送中, 1=发送成功, 2=发送失败, 3=已删除, 4=已撤回)
+- (void)saveMessageStatus:(NSInteger)status forMessageId:(NSString *)messageId;
+
+/// 获取消息状态
+/// @param messageId 消息ID
+/// @return 消息状态,如果不存在则返回1(发送成功)
+- (NSInteger)getMessageStatusForMessageId:(NSString *)messageId;
+
+/// 批量保存消息状态
+/// @param statusDict 字典,key为消息ID,value为状态值
+- (void)batchSaveMessageStatus:(NSDictionary<NSString *, NSNumber *> *)statusDict;
+
+/// 批量获取消息状态
+/// @param messageIds 消息ID数组
+/// @return 字典,key为消息ID,value为状态值
+- (NSDictionary<NSString *, NSNumber *> *)batchGetMessageStatusForMessageIds:(NSArray<NSString *> *)messageIds;
+
+/// 删除消息状态
+/// @param messageId 消息ID
+- (void)removeMessageStatusForMessageId:(NSString *)messageId;
+
+/// 批量删除消息状态
+/// @param messageIds 消息ID数组
+- (void)batchRemoveMessageStatusForMessageIds:(NSArray<NSString *> *)messageIds;
+
+/// 清空所有消息状态
+- (void)clearAllMessageStatus;
+
+/// 获取所有消息状态数量
+- (NSInteger)getAllMessageStatusCount;
+
+@end
+
+NS_ASSUME_NONNULL_END
+

+ 221 - 0
AIIM/Common/Manager/MessageStatusManager.m

@@ -0,0 +1,221 @@
+//
+//  MessageStatusManager.m
+//  AIIM
+//
+//  Created by AI Assistant on 2025/10/21.
+//
+
+#import "MessageStatusManager.h"
+
+// plist 文件名
+static NSString *const kMessageStatusPlistName = @"MessageStatus.plist";
+
+@interface MessageStatusManager ()
+
+@property (nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *statusCache;
+@property (nonatomic, strong) dispatch_queue_t ioQueue;
+@property (nonatomic, copy) NSString *plistPath;
+
+@end
+
+@implementation MessageStatusManager
+
+#pragma mark - Singleton
+
++ (instancetype)sharedInstance {
+    static MessageStatusManager *instance = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        instance = [[self alloc] init];
+    });
+    return instance;
+}
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        // 创建串行队列用于文件读写,避免并发问题
+        _ioQueue = dispatch_queue_create("com.aiim.messageStatus.io", DISPATCH_QUEUE_SERIAL);
+        
+        // 获取 plist 文件路径
+        _plistPath = [self plistFilePath];
+        
+        // 加载缓存
+        [self loadCache];
+    }
+    return self;
+}
+
+#pragma mark - Public Methods
+
+- (void)saveMessageStatus:(NSInteger)status forMessageId:(NSString *)messageId {
+    if (!messageId || messageId.length == 0) {
+        NSLog(@"[MessageStatusManager] 保存失败: messageId 为空");
+        return;
+    }
+    
+    dispatch_async(self.ioQueue, ^{
+        // 更新内存缓存
+        self.statusCache[messageId] = @(status);
+        
+        // 保存到文件
+        [self saveCacheToDisk];
+        
+        NSLog(@"[MessageStatusManager] 保存成功: messageId=%@, status=%ld", messageId, (long)status);
+    });
+}
+
+- (NSInteger)getMessageStatusForMessageId:(NSString *)messageId {
+    if (!messageId || messageId.length == 0) {
+        NSLog(@"getMessageStatusForMessageId 1: 1");
+        return 1; // 默认返回发送成功
+    }
+    
+    if (![self.statusCache.allKeys containsObject:messageId]) {
+        NSLog(@"getMessageStatusForMessageId 2: 1");
+        return 0;
+    }
+    
+    __block NSInteger status = 0; // 默认值
+    dispatch_sync(self.ioQueue, ^{
+        NSNumber *statusNumber = self.statusCache[messageId];
+        if (statusNumber) {
+            status = [statusNumber integerValue];
+        }
+    });
+    NSLog(@"getMessageStatusForMessageId 3: %ld", status);
+    return status;
+}
+
+- (void)batchSaveMessageStatus:(NSDictionary<NSString *, NSNumber *> *)statusDict {
+    if (!statusDict || statusDict.count == 0) {
+        return;
+    }
+    
+    dispatch_async(self.ioQueue, ^{
+        // 批量更新内存缓存
+        [self.statusCache addEntriesFromDictionary:statusDict];
+        
+        // 保存到文件
+        [self saveCacheToDisk];
+        
+        NSLog(@"[MessageStatusManager] 批量保存成功: %lu 条", (unsigned long)statusDict.count);
+    });
+}
+
+- (NSDictionary<NSString *, NSNumber *> *)batchGetMessageStatusForMessageIds:(NSArray<NSString *> *)messageIds {
+    if (!messageIds || messageIds.count == 0) {
+        return @{};
+    }
+    
+    __block NSMutableDictionary *result = [NSMutableDictionary dictionary];
+    dispatch_sync(self.ioQueue, ^{
+        for (NSString *messageId in messageIds) {
+            NSNumber *status = self.statusCache[messageId];
+            if (status) {
+                result[messageId] = status;
+            } else {
+                result[messageId] = @(1); // 默认发送成功
+            }
+        }
+    });
+    
+    return [result copy];
+}
+
+- (void)removeMessageStatusForMessageId:(NSString *)messageId {
+    if (!messageId || messageId.length == 0) {
+        return;
+    }
+    
+    dispatch_async(self.ioQueue, ^{
+        [self.statusCache removeObjectForKey:messageId];
+        
+        // 保存到文件
+        [self saveCacheToDisk];
+        
+        NSLog(@"[MessageStatusManager] 删除成功: messageId=%@", messageId);
+    });
+}
+
+- (void)batchRemoveMessageStatusForMessageIds:(NSArray<NSString *> *)messageIds {
+    if (!messageIds || messageIds.count == 0) {
+        return;
+    }
+    
+    dispatch_async(self.ioQueue, ^{
+        [self.statusCache removeObjectsForKeys:messageIds];
+        
+        // 保存到文件
+        [self saveCacheToDisk];
+        
+        NSLog(@"[MessageStatusManager] 批量删除成功: %lu 条", (unsigned long)messageIds.count);
+    });
+}
+
+- (void)clearAllMessageStatus {
+    dispatch_async(self.ioQueue, ^{
+        [self.statusCache removeAllObjects];
+        
+        // 删除文件
+        NSFileManager *fileManager = [NSFileManager defaultManager];
+        if ([fileManager fileExistsAtPath:self.plistPath]) {
+            NSError *error = nil;
+            [fileManager removeItemAtPath:self.plistPath error:&error];
+            if (error) {
+                NSLog(@"[MessageStatusManager] 清空失败: %@", error);
+            } else {
+                NSLog(@"[MessageStatusManager] 清空成功");
+            }
+        }
+    });
+}
+
+- (NSInteger)getAllMessageStatusCount {
+    __block NSInteger count = 0;
+    dispatch_sync(self.ioQueue, ^{
+        count = self.statusCache.count;
+    });
+    return count;
+}
+
+#pragma mark - Private Methods
+
+/// 获取 plist 文件路径
+- (NSString *)plistFilePath {
+    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+    NSString *documentsDirectory = [paths firstObject];
+    return [documentsDirectory stringByAppendingPathComponent:kMessageStatusPlistName];
+}
+
+/// 从磁盘加载缓存
+- (void)loadCache {
+    dispatch_sync(self.ioQueue, ^{
+        NSFileManager *fileManager = [NSFileManager defaultManager];
+        if ([fileManager fileExistsAtPath:self.plistPath]) {
+            NSDictionary *diskData = [NSDictionary dictionaryWithContentsOfFile:self.plistPath];
+            if (diskData) {
+                self.statusCache = [diskData mutableCopy];
+                NSLog(@"[MessageStatusManager] 加载缓存成功: %lu 条", (unsigned long)self.statusCache.count);
+            } else {
+                self.statusCache = [NSMutableDictionary dictionary];
+                NSLog(@"[MessageStatusManager] 缓存文件为空,创建新缓存");
+            }
+        } else {
+            self.statusCache = [NSMutableDictionary dictionary];
+            NSLog(@"[MessageStatusManager] 缓存文件不存在,创建新缓存");
+        }
+    });
+}
+
+/// 将缓存保存到磁盘
+- (void)saveCacheToDisk {
+    // 注意: 此方法必须在 ioQueue 中调用
+    BOOL success = [self.statusCache writeToFile:self.plistPath atomically:YES];
+    if (!success) {
+        NSLog(@"[MessageStatusManager] 保存到磁盘失败");
+    }
+}
+
+@end
+

+ 4 - 0
AIIM/Common/Network/aliOSS/OSSManager.m

@@ -12,6 +12,7 @@
 #import "ChatsStore.h"
 #import "GDBManager.h"
 #import "GWebSocket.h"
+#import "MessageStatusManager.h"
 
 // yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
 #define endpoint @"https://oss-ap-southeast-1.aliyuncs.com"//生产服务器
@@ -375,6 +376,9 @@
 
 -(void)updataFileMsg:(NSString *)thrid state:(NSString *)state{
     
+    NSInteger status = [state integerValue];
+    NSInteger value = status-1;
+    [[MessageStatusManager sharedInstance] saveMessageStatus:value forMessageId:thrid];
     
     [GDBManager.shareInstance selectLocalmsgWithLocaltime:thrid succ:^(NSArray * _Nullable array) {
         NSLog(@"selectLocalmsgWithLocaltime:%@",array);

+ 13 - 0
AIIM/Controller/chat/chetCell/ChatMessageModel.h

@@ -25,7 +25,15 @@ typedef NS_ENUM(NSUInteger, ChatMessageType) {
     ChatMessageTypeDFBUSY = 13,// 对方正在忙
 };
 
+/// 消息状态
+typedef NS_ENUM(NSInteger, MessageSendStatus){
+    MessageSending             = 0,  ///< 消息发送中
+    MessageSent                = 1,  ///< 消息发送成功
+    MessageSendFail            = 2,  ///< 消息发送失败
+};
+
 @interface ChatMessageModel : NSObject
+@property (nonatomic, assign) MessageSendStatus messageSendStatus;
 @property (nonatomic, assign) ChatMessageType messageType;
 @property (nonatomic, assign) NSInteger type; //0=友聊 1=群聊
 @property (nonatomic, assign) BOOL isSender; // 是否是发送方
@@ -78,6 +86,7 @@ typedef NS_ENUM(NSUInteger, ChatMessageType) {
 @property (nonatomic, strong) NSArray * forwardMsgArray;
 
 @property (nonatomic,  copy) NSDictionary * formerMessage;
+@property (nonatomic, assign, readonly) BOOL isUploadFile;
 
 // 从字典初始化方法
 + (instancetype)modelWithDictionary:(NSDictionary *)dict;
@@ -86,6 +95,10 @@ typedef NS_ENUM(NSUInteger, ChatMessageType) {
 - (void)setupWithDictionary:(NSDictionary *)dict;
 // 从模型设置属性方法
 - (void)exchangeModelWithModel:(ChatMessageModel *)model;
+// 更新消息状态到 plist
+- (void)updateMessageStatus:(MessageSendStatus)status;
+// 从 plist 刷新消息状态
+- (void)refreshMessageStatus;
 // 生成缩略图的方法,同时判断是否开始下载视频
 - (void)generateThumbnailWithCompletion:(void(^)(UIImage *thumbnail))completion;
 //下载文件保存本地

+ 33 - 0
AIIM/Controller/chat/chetCell/ChatMessageModel.m

@@ -11,6 +11,7 @@
 #import "AVFoundation/AVFoundation.h"
 #import "FileNetApi.h"
 #import <SDWebImage/SDImageCache.h>
+#import "MessageStatusManager.h"
 
 @implementation ChatMessageModel
 
@@ -119,6 +120,10 @@
     self.timestamp = [mutableDict[@"timestamp"] longLongValue];
     self.localtime = [mutableDict[@"localtime"] longLongValue];
     self.formatterTime = [self timeF:self.timestamp];
+    
+    // 从 plist 读取状态
+    self.messageSendStatus = [[MessageStatusManager sharedInstance] getMessageStatusForMessageId:self.msgId];
+    
     // 其他自定义字段...
     self.readStatus = [self getReadStatus];
     
@@ -166,6 +171,8 @@
     self.timestamp = model.timestamp;
     self.localtime = model.localtime;
     self.formatterTime = model.formatterTime;
+    // 消息状态
+    self.messageSendStatus = model.messageSendStatus;
 //    // 其他自定义字段...
     // 其他自定义字段...
     self.readStatus = [self getReadStatus];
@@ -475,6 +482,25 @@
     return width;
 }
 
+#pragma mark - 消息状态管理
+
+// 更新消息状态到 plist
+- (void)updateMessageStatus:(MessageSendStatus)status {
+    self.messageSendStatus = status;
+    if (self.msgId.length > 0) {
+        [[MessageStatusManager sharedInstance] saveMessageStatus:status forMessageId:self.msgId];
+        NSLog(@"[ChatMessageModel] 更新消息状态: msgId=%@, status=%ld", self.msgId, (long)status);
+    }
+}
+
+// 从 plist 刷新消息状态
+- (void)refreshMessageStatus {
+    if (self.msgId.length > 0) {
+        NSInteger status = [[MessageStatusManager sharedInstance] getMessageStatusForMessageId:self.msgId];
+        self.messageSendStatus = (MessageSendStatus)status;
+    }
+}
+
 - (NSString *)getFileSize {
     NSString *filePath = self.localurl;
 
@@ -528,4 +554,11 @@
     return nil;
 }
 
+- (BOOL)isUploadFile {
+    return self.messageType == ChatMessageTypeFile || 
+    self.messageType == ChatMessageTypeVoice || 
+    self.messageType == ChatMessageTypeImage ||
+    self.messageType == ChatMessageTypeVideo;
+}
+
 @end

+ 1 - 0
AIIM/Controller/chat/chetCell/chatCellView.h

@@ -23,6 +23,7 @@
 @property (nonatomic,copy) void(^bubbleCellLongPressMenuBlock)(NSInteger index);
 @property (nonatomic,copy) void(^batchSelectedStateBlock)(BOOL isSelected);
 @property (nonatomic,copy) void(^avatarTapBlock)(ChatMessageModel * message);
+@property (nonatomic,copy) void(^resendMessageBlock)(ChatMessageModel * message);
 @property (nonatomic) CGFloat height;
 @property (nonatomic, strong) ChatMessageModel *messageModel;
 

+ 187 - 76
AIIM/Controller/chat/chetCell/chatCellView.m

@@ -46,6 +46,14 @@ static const CGFloat kMediaCornerRadius = 4.0f;
 @property (nonatomic, strong) UILabel * readStateLbl;
 @property (nonatomic, strong) UIButton * batchStateBtn;
 
+// 发送进度圆环
+@property (nonatomic, strong) CAShapeLayer *progressLayer;
+@property (nonatomic, strong) CAShapeLayer *progressBackgroundLayer;
+@property (nonatomic, strong) UIView *progressContainerView;
+
+// 发送失败按钮
+@property (nonatomic, strong) UIButton *resendButton;
+
 // 内容视图
 @property (nonatomic, strong) UILabel *textLbl;
 @property (nonatomic, strong) UIImageView *imageContentView;
@@ -62,7 +70,7 @@ static const CGFloat kMediaCornerRadius = 4.0f;
 @property (nonatomic, strong) UILabel * deleateLbl;
 @property (nonatomic, strong) UILabel * quoteLbl;
 
-@property (nonatomic, strong) UILabel * jinduLbl;
+//@property (nonatomic, strong) UILabel * jinduLbl;
 
 @property (strong, nonatomic) AVPlayer *player;
 @property (strong, nonatomic) AVPlayerViewController *playerViewController;
@@ -89,6 +97,24 @@ static const CGFloat kMediaCornerRadius = 4.0f;
     return self;
 }
 
+- (void)layoutSubviews {
+    [super layoutSubviews];
+    
+    if (_currentContentView && _messageModel.isSender) {
+        CGFloat offset = _messageModel.messageType == ChatMessageTypeFile ? 16 : 8;
+        [_progressContainerView mas_remakeConstraints:^(MASConstraintMaker *make) {
+            make.width.height.mas_equalTo(30);
+            make.right.equalTo(_currentContentView.mas_left).offset(-offset);
+            make.centerY.equalTo(_currentContentView);
+        }];
+        
+        [_resendButton mas_remakeConstraints:^(MASConstraintMaker *make) {
+            make.right.equalTo(_currentContentView.mas_left).offset(-8);
+            make.centerY.equalTo(_currentContentView);
+        }];
+    }
+}
+
 
 -(void)initAlllSubviews{
     //复选框
@@ -146,9 +172,117 @@ static const CGFloat kMediaCornerRadius = 4.0f;
     [self setupEventContentView];
     [self setupCallbackContentView];
     [self setupdeleateContentView];
+    
+    // 初始化进度圆环
+    [self setupProgressView];
+    
+    // 初始化发送失败按钮
+    [self setupResendButton];
+    
 }
 
 
+#pragma mark - 进度圆环配置
+- (void)setupProgressView {
+    // 容器视图
+    _progressContainerView = [[UIView alloc] init];
+    _progressContainerView.hidden = YES;
+    [self.contentView addSubview:_progressContainerView];
+    
+    // 圆环尺寸
+    CGFloat size = 30.0f;
+    CGFloat lineWidth = 2.0f;
+    CGRect circleFrame = CGRectMake(0, 0, size, size);
+    UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(size/2, size/2)
+                                                              radius:(size - lineWidth) / 2
+                                                          startAngle:-M_PI_2
+                                                            endAngle:M_PI_2 * 3
+                                                           clockwise:YES];
+    
+    // 背景圆环
+    _progressBackgroundLayer = [CAShapeLayer layer];
+    _progressBackgroundLayer.path = circlePath.CGPath;
+    _progressBackgroundLayer.fillColor = [UIColor clearColor].CGColor;
+    _progressBackgroundLayer.strokeColor = [UIColor colorWithWhite:1.0 alpha:0.3].CGColor;
+    _progressBackgroundLayer.lineWidth = lineWidth;
+    _progressBackgroundLayer.frame = circleFrame;
+    [_progressContainerView.layer addSublayer:_progressBackgroundLayer];
+    
+    // 进度圆环
+    _progressLayer = [CAShapeLayer layer];
+    _progressLayer.path = circlePath.CGPath;
+    _progressLayer.fillColor = [UIColor clearColor].CGColor;
+    _progressLayer.strokeColor = [UIColor whiteColor].CGColor;
+    _progressLayer.lineWidth = lineWidth;
+    _progressLayer.lineCap = kCALineCapRound;
+    _progressLayer.strokeEnd = 0.0f;
+    _progressLayer.frame = circleFrame;
+    [_progressContainerView.layer addSublayer:_progressLayer];
+}
+
+// 更新进度圆环
+- (void)updateProgress:(CGFloat)progress {
+    if (progress < 0) progress = 0;
+    if (progress > 1) progress = 1;
+    
+    dispatch_async(dispatch_get_main_queue(), ^{
+        self.progressLayer.strokeEnd = progress;
+        
+//        // 如果进度完成,隐藏圆环
+//        if (progress >= 1.0) {
+//            self.progressContainerView.hidden = YES;
+//        }
+    });
+}
+
+// 显示进度圆环
+- (void)showProgressView {
+    dispatch_async(dispatch_get_main_queue(), ^{
+        self.progressContainerView.hidden = NO;
+        self.progressLayer.strokeEnd = 0.0f;
+    });
+}
+
+// 隐藏进度圆环
+- (void)hideProgressView {
+    dispatch_async(dispatch_get_main_queue(), ^{
+        self.progressContainerView.hidden = YES;
+    });
+}
+
+#pragma mark - 发送失败按钮配置
+- (void)setupResendButton {
+    _resendButton = [UIButton buttonWithType:UIButtonTypeCustom];
+    [_resendButton setImage:[UIImage imageNamed:@"icon_resend"] forState:UIControlStateNormal];
+    _resendButton.hidden = YES;
+    [_resendButton addTarget:self action:@selector(handleResendButtonTap) forControlEvents:UIControlEventTouchUpInside];
+    [self.contentView addSubview:_resendButton];
+}
+
+// 显示重发按钮
+- (void)showResendButton {
+    dispatch_async(dispatch_get_main_queue(), ^{
+        self.resendButton.hidden = NO;
+    });
+}
+
+// 隐藏重发按钮
+- (void)hideResendButton {
+    dispatch_async(dispatch_get_main_queue(), ^{
+        self.resendButton.hidden = YES;
+    });
+}
+
+// 重发按钮点击事件
+- (void)handleResendButtonTap {
+    NSLog(@"点击重发按钮: msgId=%@", _messageModel.msgId);
+    
+    // 触发重发回调
+    if (self.resendMessageBlock) {
+        self.resendMessageBlock(_messageModel);
+    }
+}
+
 #pragma mark - 内容视图配置
 - (void)setupTextContentView {
     _textLbl = [UILabel new];
@@ -232,25 +366,6 @@ static const CGFloat kMediaCornerRadius = 4.0f;
         make.width.height.mas_equalTo(40);
     }];
     
-    
-    _jinduLbl = [UILabel new];
-    [_videoContentView addSubview:_jinduLbl];
-    _jinduLbl.alpha = 0;
-    _jinduLbl.numberOfLines = 1;
-    _jinduLbl.font = SYSFONT(16);
-    _jinduLbl.textColor = UIColor.whiteColor;
-    _jinduLbl.layer.cornerRadius = 20.f;
-    _jinduLbl.layer.masksToBounds = YES;
-    _jinduLbl.textAlignment = NSTextAlignmentCenter;
-    _jinduLbl.backgroundColor =[UIColor colorWithRed:0 green:0 blue:0 alpha:0.7];
-    _jinduLbl.text = @"99";
-    [_jinduLbl mas_makeConstraints:^(MASConstraintMaker *make) {
-        make.left.equalTo(_videoContentView).offset(8);
-        make.top.equalTo(_videoContentView).offset(8);
-        make.width.height.mas_equalTo(40);
-    }];
-    
-    
     [self.contentView addSubview:_videoContentView];
     
     // 添加点击手势
@@ -505,6 +620,9 @@ static const CGFloat kMediaCornerRadius = 4.0f;
     //长按事件
     [self setupLongPressEvent];
     
+    // 配置发送进度
+    [self setupSendingProgress];
+    
 }
 #pragma mark - 批量状态
 - (void)setBatchState:(BOOL)batchState{
@@ -618,6 +736,52 @@ static const CGFloat kMediaCornerRadius = 4.0f;
     }
 }
 
+// 配置发送进度
+- (void)setupSendingProgress {
+    // 只有发送方才显示进度或失败按钮
+    if (!_messageModel.isSender || !_messageModel.isUploadFile) {
+        [self hideProgressView];
+        [self hideResendButton];
+        return;
+    }
+        
+    // 根据消息状态显示进度或失败按钮
+    switch (_messageModel.messageSendStatus) {
+        case     MessageSending: {
+            // 发送中,显示进度圆环,隐藏失败按钮
+            [self showProgressView];
+            [self hideResendButton];
+            
+            // 如果有上传进度回调,监听进度
+            weakSelf(self);
+            if (_messageModel.isUploadFile) {
+                _messageModel.uploadPersentChange = ^(NSInteger persent, NSInteger localtime) {
+                    if (localtime == weakself.messageModel.localtime) {
+                        CGFloat progress = persent / 100.0f;
+                        [weakself updateProgress:progress];
+                        if (progress >= 1) {
+                            weakself.messageModel.messageSendStatus = MessageSent;
+                        }
+                    }
+                };
+            }
+            break;
+        }
+        case     MessageSendFail: {
+            // 发送失败,显示失败按钮,隐藏进度圆环
+            [self hideProgressView];
+            [self showResendButton];
+            break;
+        }
+        case     MessageSent:
+            // 其他状态隐藏进度和失败按钮
+            [self hideProgressView];
+            [self hideResendButton];
+            break;
+    }
+    
+}
+
 - (void)longPressEvent:(UILongPressGestureRecognizer *)gestureRecognizer {
     if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
         NSLog(@"长按事件开始");
@@ -901,44 +1065,6 @@ static const CGFloat kMediaCornerRadius = 4.0f;
     }
     
 
-    _messageModel.uploadPersentChange = ^(NSInteger index,NSInteger localtime) {
-        
-        if(localtime==weakself.messageModel.localtime){
-            if(index>=100){
-                dispatch_async(dispatch_get_main_queue(), ^{
-                    if(weakself.jinduLbl){
-                        weakself.jinduLbl.alpha = 0;
-                        weakself.loadingImg.alpha = 0;
-                    }
-                });
-            }
-            else{
-                dispatch_async(dispatch_get_main_queue(), ^{
-                    if(weakself.jinduLbl){
-                        if(weakself.loadingImg.alpha!=1||!weakself.loadingImg.image){
-                            NSString *gifPath = [[NSBundle mainBundle] pathForResource:@"load" ofType:@"gif"];
-                            NSData *gifData = [NSData dataWithContentsOfFile:gifPath];
-                            UIImage *placeholder = [UIImage sd_imageWithGIFData:gifData];
-                            weakself.loadingImg.image = placeholder;
-                            weakself.loadingImg.alpha = 1;
-                        }
-                        weakself.jinduLbl.alpha = 1;
-                        weakself.jinduLbl.text = [NSString stringWithFormat:@"%ld",(long)index];
-                    }
-                });
-            }
-        }
-        else{
-            dispatch_async(dispatch_get_main_queue(), ^{
-                if(weakself.jinduLbl){
-                    weakself.jinduLbl.alpha = 0;
-                    weakself.loadingImg.alpha = 0;
-                }
-            });
-        }
-    };
-    
-    
     _currentContentView = _videoContentView;
 }
 
@@ -1150,27 +1276,12 @@ static const CGFloat kMediaCornerRadius = 4.0f;
             make.top.mas_equalTo(self.avatarImageView);
         }];
         
-        if (_messageModel.messageType == ChatMessageTypeImage || _messageModel.messageType == ChatMessageTypeVideo) {
+        if (_currentContentView) {
             [_readStateLbl mas_remakeConstraints:^(MASConstraintMaker *make) {
-                make.right.mas_equalTo(self.contentView).offset(-30);
+                make.left.mas_equalTo(_currentContentView.mas_right).offset(6);
                 make.height.mas_equalTo(14);
-                make.top.mas_equalTo(self.currentContentView.mas_bottom).offset(2);
+                make.bottom.mas_equalTo(_currentContentView.mas_bottom);
             }];
-        }else{
-            if (!_quoteLbl.hidden) {
-                [_readStateLbl mas_remakeConstraints:^(MASConstraintMaker *make) {
-                    make.right.mas_equalTo(self.contentView).offset(-30);
-                    make.height.mas_equalTo(14);
-                    make.top.mas_equalTo(self.quoteLbl.mas_bottom);
-                }];
-            }else{
-                [_readStateLbl mas_remakeConstraints:^(MASConstraintMaker *make) {
-                    make.right.mas_equalTo(self.contentView).offset(-30);
-                    make.height.mas_equalTo(14);
-                    make.top.mas_equalTo(self.bubbleImageView.mas_bottom);
-                }];
-            }
-            
         }
     } else {
         [_avatarImageView mas_remakeConstraints:^(MASConstraintMaker *make) {