// // ChatMessageModel.m // AIIM // // Created by qitewei on 2025/5/14. // #import "ChatMessageModel.h" #import "CryptoAES.h" #import "ChatsStore.h" #import "AVFoundation/AVFoundation.h" #import "FileNetApi.h" @implementation ChatMessageModel + (instancetype)modelWithDictionary:(NSDictionary *)dict { ChatMessageModel *model = [[ChatMessageModel alloc] init]; [model setupWithDictionary:dict]; return model; } - (void)setupWithDictionary:(NSDictionary *)dict { NSMutableDictionary * mutableDict = [NSMutableDictionary dictionaryWithDictionary:dict]; [mutableDict jk_each:^(id k, id v) { if ([v isKindOfClass:[NSNull class]]) { [mutableDict setObject:@"" forKey:k]; } }]; //原消息 self.formerMessage = dict; self.chatId = mutableDict[@"chatId"]; // 消息类型 NSInteger type = [mutableDict[@"messageType"] integerValue]; self.messageType = (ChatMessageType)type; self.type = [mutableDict[@"type"] integerValue]; self.nickName = mutableDict[@"fromName"]?:@""; self.avatar = mutableDict[@"fromAvatar"]?:@""; // 通用内容 // self.content = mutableDict[@"content"] ?: @"";//本地明文存储 self.content =[CryptoAES.shareInstance decryptDataL:mutableDict[@"content"] ?: @""];//本地加密存储 self.msgId = mutableDict[@"id"]; self.fromId = mutableDict[@"fromId"]; // 发送方标识 NSDictionary *userinfo = [UDManager.shareInstance getDDManager:dkuserinfo]; NSString *userId = userinfo[@"id"]; if([self.fromId isEqualToString:userId]){ self.isSender=true; } else{ self.isSender=false; } // 处理媒体尺寸 CGFloat width = [mutableDict[@"width"] floatValue]; CGFloat height = [mutableDict[@"height"] floatValue]; if (width > 0 && height > 0) { self.mediaSize = CGSizeMake(width, height); } else { // 默认尺寸 self.mediaSize = CGSizeMake(200, 150); } self.placeholderImage = kImageMake(@"pictrue_placeholder"); // 文件消息 NSDictionary * extend = [mutableDict[@"extend"] isKindOfClass:NSDictionary.class]?mutableDict[@"extend"]:@{}; self.fileName = extend[@"fileName"] ?: @"未知文件"; self.fileSize = extend[@"size"] ?: @"0KB"; self.url = extend[@"url"] ?: @""; NSCharacterSet *allowedCharacters = [NSCharacterSet URLQueryAllowedCharacterSet]; self.url = [self.url stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; self.localurl = extend[@"localurl"] ?: @""; self.quoteMessage = ![extend jk_hasKey:@"quoteMessage"] ?NULL: [ChatMessageModel modelWithDictionary:extend[@"quoteMessage"]]; // 语音消息 NSInteger duration = [extend[@"time"] integerValue]; if (duration<=60) { self.voiceDuration = duration; }else{ self.voiceDuration = duration/1000; } self.voiceWidth = self.voiceDuration==0?0:[self voiceBubbleWidth]; //文件传输成功或失败 self.fileError = 0; NSInteger fileError = [extend[@"fileError"] integerValue]; if (fileError!=0) { self.fileError = fileError; } else{ if(self.url.length>0){ self.fileError = 0; } } //合并转发 self.forwardMsgArray = extend[@"messageList"] ?:@[]; // 通话记录 self.callDuration = [mutableDict[@"duration"] integerValue]; self.isCallSuccess = [mutableDict[@"result"] boolValue]; self.isVideo = [mutableDict[@"video"] boolValue]; //@相关 self.atAll = extend[@"atAll"]?[extend[@"atAll"] boolValue]:NO; self.atUserIds = extend[@"atUserIds"]?:@[]; // 时间戳(如果需要) self.timestamp = [mutableDict[@"timestamp"] longLongValue]; self.localtime = [mutableDict[@"localtime"] longLongValue]; self.formatterTime = [self timeF:self.timestamp]; // 其他自定义字段... self.readStatus = [self getReadStatus]; } - (void)exchangeModelWithModel:(ChatMessageModel *)model{ self.msgId = model.msgId; self.formerMessage = model.formerMessage; if(self.messageType!=model.messageType){ self.cellHeight=0; } self.messageType = model.messageType; self.type = model.type; self.isSender = model.isSender; self.content = model.content; self.msgId = model.msgId; self.mediaSize = model.mediaSize; self.placeholderImage = model.placeholderImage; self.fileName = model.fileName; self.fileSize = model.fileSize; self.url = model.url; self.localurl = model.localurl; //文件传输成功或失败 self.fileError = model.fileError; // 通话记录 self.callDuration = model.callDuration; self.isCallSuccess = model.isCallSuccess; self.isVideo = model.isVideo; self.avatar = model.avatar; self.nickName = model.nickName; //@相关 self.atAll = model.atAll; self.atUserIds = model.atUserIds; // 时间戳(如果需要) self.timestamp = model.timestamp; self.localtime = model.localtime; self.formatterTime = model.formatterTime; // // 其他自定义字段... // 其他自定义字段... self.readStatus = [self getReadStatus]; } -(NSString *)timeF:(NSInteger)time{ //NSLog(@"time:%ld",(long)time); // 创建日期格式器 NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setDateFormat:@"dd"]; // 设置你想要的日期格式 long long timestamp =(long)time/1000; // 例如:2021-10-01 00:00:00 UTC // 转换为NSDate NSDate *date = [NSDate dateWithTimeIntervalSince1970:timestamp]; NSString *dateString = [formatter stringFromDate:date]; NSDate *now = [NSDate date]; NSTimeInterval trt = [now timeIntervalSince1970]; date = [NSDate dateWithTimeIntervalSince1970:trt]; NSString *tdateString = [formatter stringFromDate:date]; NSInteger shijiancha = trt-timestamp; if(shijiancha<60){ return NSLocalizedString(@"time-oneminint", @""); } if(shijiancha>24*3600||dateString.intValue!=tdateString.intValue){ // 假设这是你的时间戳(以秒为单位) long long timestamp =(long)time/1000; // 例如:2021-10-01 00:00:00 UTC // 转换为NSDate NSDate *date = [NSDate dateWithTimeIntervalSince1970:timestamp]; // 创建日期格式器 NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setDateFormat:@"yyyy-MM-dd HH:mm"]; // 设置你想要的日期格式 // 转换为字符串 NSString *dateString = [formatter stringFromDate:date]; return dateString; } else{ NSInteger xiaoshi = shijiancha/3600; NSInteger fenzhong = shijiancha/60; if(xiaoshi>0){ // 假设这是你的时间戳(以秒为单位) long long timestamp =(long)time/1000; // 例如:2021-10-01 00:00:00 UTC // 转换为NSDate NSDate *date = [NSDate dateWithTimeIntervalSince1970:timestamp]; // 创建日期格式器 NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setDateFormat:@"HH:mm"]; // 设置你想要的日期格式 // 转换为字符串 NSString *dateString = [formatter stringFromDate:date]; return dateString; } else{ return [NSString stringWithFormat:@"%ld%@",(long)fenzhong,NSLocalizedString(@"time-outmin", @"")]; } } return @""; } - (NSString *)getReadStatus{ NSString * readStatus; self.readstate = 0; if(self.timestamp == 0){ return @""; } if(self.messageType==11||self.messageType==12){ return @""; } if(self.type==0){ if (self.localtime != 0 && self.localtime == self.timestamp) { NSDate *now = [NSDate date]; NSTimeInterval trt = [now timeIntervalSince1970]; NSInteger time = trt*1000; NSInteger outTime =time-self.localtime; if(outTime>33000){ if(self.messageType==1||self.messageType==2||self.messageType==5){ readStatus = @"正在发送"; self.readstate = 3; }else{ readStatus = @"发送失败"; self.readstate = 4; } }else{ readStatus = @"正在发送"; self.readstate = 3; } } else{ //判断已读未读 if(self.timestamp>ChatsStore.shareInstance.lastreadTime){ readStatus = @"未读"; self.readstate = 1; } else{ readStatus = @"已读"; self.readstate = 2; } if(self.fileError==1){ readStatus = @"發送失敗,點擊重發"; self.readstate = 4; } } }else{ return @""; } return readStatus; } - (void)generateThumbnailWithCompletion:(void(^)(UIImage *thumbnail))completion { if (self.videoThumbnailImage) { if (completion) completion(self.videoThumbnailImage); return; } NSURL *videoURL = nil; if (self.localurl.length != 0 && [[NSFileManager defaultManager] fileExistsAtPath:self.localurl]) { NSLog(@"generateThumbnailWithCompletion localurl:%@",self.localurl); videoURL = [NSURL fileURLWithPath:self.localurl]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoURL options:nil]; AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:asset]; generator.appliesPreferredTrackTransform = YES; CMTime acttime; NSError *error = nil; CGImageRef imageRef = [generator copyCGImageAtTime:CMTimeMake(0, 600) actualTime:&acttime error:&error]; NSLog(@"error:%@",error); if (!error && imageRef) { UIImage *thumbnail = [[UIImage alloc] initWithCGImage:imageRef]; CGImageRelease(imageRef); // 缓存到内存 self.videoThumbnailImage = thumbnail; if (completion){ completion(thumbnail); } } else { if (completion) completion(nil); } }); }else{ NSLog(@"generateThumbnailWithCompletion url"); if (self.url.length == 0){ completion(nil); return; } NSDictionary *options = @{AVURLAssetAllowsCellularAccessKey: @YES, AVURLAssetPreferPreciseDurationAndTimingKey: @YES}; AVURLAsset *asset = [AVURLAsset URLAssetWithURL:getURL(self.url) options:options]; // 创建图像生成器 AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset]; generator.appliesPreferredTrackTransform = YES; // 应用视频的方向 generator.maximumSize = CGSizeMake(320, 0); // 设置最大尺寸,保持宽高比 // 异步生成缩略图 [generator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:kCMTimeZero]] completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) { __block UIImage *thumbnail = nil; // 处理生成结果 if (result == AVAssetImageGeneratorSucceeded && image) { // 创建UIImage,注意CGImageCreateCopy不是必需的 thumbnail = [UIImage imageWithCGImage:image]; self.videoThumbnailImage = thumbnail; // 重要:虽然UIImage会retain CGImageRef,但最佳实践是手动管理 // 这里不需要调用CGImageRelease,因为imageWithCGImage会自动retain } dispatch_async(dispatch_get_main_queue(), ^{ if (completion) completion(thumbnail); }); }]; } } //下载视频文件 -(void)xiazaishipin:(void(^)(NSInteger persent))loading{ NSLog(@"xiazaishipin--------"); if(!self.isSender){ NSString *localFileurl = [self.localurl stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]]; if (self.localurl.length != 0 && [[NSFileManager defaultManager] fileExistsAtPath:localFileurl]){ // NSLog(@"文件已经存在本地"); if (loading) { loading(100); } } else{ // NSLog(@"文件不存在"); if(self.url&&self.url.length != 0){ NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil]; NSURL *tempUrl = [NSURL URLWithString:self.url]; NSString *fileName = tempUrl.lastPathComponent; NSString *fileExtension = [fileName pathExtension]; NSString *newfileName = [NSString stringWithFormat:@"%@.%@", self.msgId,fileExtension]; documentsDirectoryURL = [documentsDirectoryURL URLByAppendingPathComponent:newfileName]; NSLog(@"文件已经存在本地1-:%@",documentsDirectoryURL.path); if ([[NSFileManager defaultManager] fileExistsAtPath:documentsDirectoryURL.path]){ NSLog(@"文件已经存在本地2-:%@",documentsDirectoryURL.path); NSMutableDictionary *mutableDict = [self.formerMessage mutableCopy]; NSMutableDictionary *extend = [self.formerMessage[@"extend"] mutableCopy]; [extend setObject:documentsDirectoryURL.path forKey:@"localurl"]; [mutableDict setObject:extend forKey:@"extend"]; self.localurl = documentsDirectoryURL.path; NSDictionary *NewMsg = [mutableDict copy]; [ChatsStore.shareInstance reciveMsg:NewMsg]; if (loading) { loading(100); } } else{ [FileNetApi downLoadWToken:getURL(self.url) thrid:self.msgId succ:^(int code, NSDictionary * res) { if(res!=nil){ NSMutableDictionary *mutableDict = [self.formerMessage mutableCopy]; NSMutableDictionary *extend = [self.formerMessage[@"extend"] mutableCopy]; [extend setObject:res[@"filePath"] forKey:@"localurl"]; [mutableDict setObject:extend forKey:@"extend"]; self.localurl = res[@"filePath"]; NSDictionary *NewMsg = [mutableDict copy]; [ChatsStore.shareInstance reciveMsg:NewMsg]; if (loading) { loading(100); } } else{ if (code>=0&&code<200) { if (loading) { loading(code); } } } } fail:^(NSError * _Nonnull error) { NSLog(@"error:%@",error); }]; } } else{ if (loading) { loading(0); } } } } else{ NSLog(@"downLoadWToken xiazaishipin-----"); } } -(void)xiazaiwenjian:(void(^)(NSInteger persent))loading{ NSLog(@"xiazaiwenjian---"); if (self.isSender) { return; } else{ // NSLog(@"self.localurl2:%@",self.localurl); if (self.localurl.length != 0 && [[NSFileManager defaultManager] fileExistsAtPath:self.localurl]){ // NSLog(@"文件已经存在本地"); if (loading) { loading(100); } return; } else{ if(self.url&&self.url.length != 0){ NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil]; NSURL *tempUrl = [NSURL URLWithString:self.url]; NSString *fileName = tempUrl.lastPathComponent; NSString *fileExtension = [fileName pathExtension]; NSString *newfileName = [NSString stringWithFormat:@"%@.%@", self.msgId,fileExtension]; documentsDirectoryURL = [documentsDirectoryURL URLByAppendingPathComponent:newfileName]; NSLog(@"文件已经存在本地1-:%@",documentsDirectoryURL.path); if ([[NSFileManager defaultManager] fileExistsAtPath:documentsDirectoryURL.path]){ NSLog(@"文件已经存在本地2-:%@",documentsDirectoryURL.path); NSMutableDictionary *mutableDict = [self.formerMessage mutableCopy]; NSMutableDictionary *extend = [self.formerMessage[@"extend"] mutableCopy]; [extend setObject:documentsDirectoryURL.path forKey:@"localurl"]; [mutableDict setObject:extend forKey:@"extend"]; self.localurl = documentsDirectoryURL.path; NSDictionary *NewMsg = [mutableDict copy]; [ChatsStore.shareInstance reciveMsg:NewMsg]; if (loading) { loading(100); } } else{ [FileNetApi downLoadWToken:getURL(self.url) thrid:self.msgId succ:^(int code, NSDictionary * res) { if(res!=nil){ NSMutableDictionary *mutableDict = [self.formerMessage mutableCopy]; NSMutableDictionary *extend = [self.formerMessage[@"extend"] mutableCopy]; [extend setObject:res[@"filePath"] forKey:@"localurl"]; [mutableDict setObject:extend forKey:@"extend"]; self.localurl = res[@"filePath"]; NSDictionary *NewMsg = [mutableDict copy]; [ChatsStore.shareInstance reciveMsg:NewMsg]; if (loading) { loading(100); } } else{ if (code>=0&&code<200) { if (loading) { loading(code); } } } } fail:^(NSError * _Nonnull error) { NSLog(@"error:%@",error); }]; } } else{ if (loading) { loading(0); } } } } } -(void)xiazaiyuyin:(void(^)(NSInteger persent))loading{ NSLog(@"xiazaiwenjian---"); if (self.isSender) { return; } else{ // NSLog(@"self.localurl2:%@",self.localurl); if (self.localurl.length != 0 && [[NSFileManager defaultManager] fileExistsAtPath:self.localurl]){ // NSLog(@"文件已经存在本地"); if (loading) { loading(100); } return; } if(self.url&&self.url.length != 0){ NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil]; NSURL *tempUrl = [NSURL URLWithString:self.url]; NSString *fileName = tempUrl.lastPathComponent; NSString *fileExtension = [fileName pathExtension]; NSString *newfileName = [NSString stringWithFormat:@"%@.%@", self.msgId,fileExtension]; documentsDirectoryURL = [documentsDirectoryURL URLByAppendingPathComponent:newfileName]; NSLog(@"文件已经存在本地1-:%@",documentsDirectoryURL.path); if ([[NSFileManager defaultManager] fileExistsAtPath:documentsDirectoryURL.path]){ NSLog(@"文件已经存在本地2-:%@",documentsDirectoryURL.path); NSMutableDictionary *mutableDict = [self.formerMessage mutableCopy]; NSMutableDictionary *extend = [self.formerMessage[@"extend"] mutableCopy]; [extend setObject:documentsDirectoryURL.path forKey:@"localurl"]; [mutableDict setObject:extend forKey:@"extend"]; self.localurl = documentsDirectoryURL.path; NSDictionary *NewMsg = [mutableDict copy]; [ChatsStore.shareInstance reciveMsg:NewMsg]; if (loading) { loading(100); } } else{ [FileNetApi downLoadWToken:getURL(self.url) thrid:self.msgId succ:^(int code, NSDictionary * res) { if(res!=nil){ NSMutableDictionary *mutableDict = [self.formerMessage mutableCopy]; NSMutableDictionary *extend = [self.formerMessage[@"extend"] mutableCopy]; [extend setObject:res[@"filePath"] forKey:@"localurl"]; [mutableDict setObject:extend forKey:@"extend"]; self.localurl = res[@"filePath"]; NSDictionary *NewMsg = [mutableDict copy]; [ChatsStore.shareInstance reciveMsg:NewMsg]; if (loading) { loading(100); } } else{ if (code>=0&&code<200) { if (loading) { loading(code); } } } } fail:^(NSError * _Nonnull error) { NSLog(@"error:%@",error); }]; } } else{ if (loading) { loading(0); } } } } -(void)setuploadPersent:(NSInteger)persent localtime:(NSInteger)localtime{ if(self.uploadPersentChange && localtime==self.localtime){ self.uploadPersentChange(persent,localtime); } } // 根据语音时长计算气泡宽度 - (CGFloat)voiceBubbleWidth { // 基础宽度 CGFloat minWidth = 60.0f; CGFloat maxWidth = 200.0f; // 最大限制时长(如60秒) NSInteger maxDuration = 60; NSInteger duration = MIN(self.voiceDuration, maxDuration); // 线性增长计算 CGFloat width = minWidth + (maxWidth - minWidth) * (duration / (CGFloat)maxDuration); return width; } @end