This commit is contained in:
启星
2025-08-08 10:49:36 +08:00
parent 6400cf78bb
commit b5ce3d580a
8780 changed files with 978183 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
/**
*
*
* This file declares the TUIFaceMessageCellData class.
* This class inherits from TUIMessageCellData and is used to store a series of data and information required by the emoticon message unit.
*/
#import <TIMCommon/TUIBubbleMessageCellData.h>
#import <TIMCommon/TUIMessageCellData.h>
NS_ASSUME_NONNULL_BEGIN
/**
* 【Module name】TUIFaceMessageCellData
* 【Function description】Emoticon message unit data source.
* - The emoticon message unit is the message unit used and displayed when displaying animated emoticons.
* - The emoticon message unit data source is a class that provides a series of required data for the display of the emoticon message unit.
*/
@interface TUIFaceMessageCellData : TUIBubbleMessageCellData
/**
*
* The index of emoticon groups
* - The subscript of the group where the emoticon is located, which is used to locate the emoticon group where the emoticon is located.
*/
@property(nonatomic, assign) NSInteger groupIndex;
/**
* The path of the emoticon file
*/
@property(nonatomic, strong) NSString *path;
/**
* The name of emoticon.
*/
@property(nonatomic, strong) NSString *faceName;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,34 @@
//
// TFaceMessageCellData.m
// TXIMSDK_TUIKit_iOS
//
// Created by annidyfeng on 2019/5/21.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIFaceMessageCellData.h"
#import <TIMCommon/TIMDefine.h>
@implementation TUIFaceMessageCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
V2TIMFaceElem *elem = message.faceElem;
TUIFaceMessageCellData *faceData = [[TUIFaceMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
faceData.groupIndex = elem.index;
faceData.faceName = [[NSString alloc] initWithData:elem.data encoding:NSUTF8StringEncoding];
for (TUIFaceGroup *group in [TIMConfig defaultConfig].faceGroups) {
if (group.groupIndex == faceData.groupIndex) {
NSString *path = [group.groupPath stringByAppendingPathComponent:faceData.faceName];
faceData.path = path;
break;
}
}
faceData.reuseId = TFaceMessageCell_ReuseId;
return faceData;
}
+ (NSString *)getDisplayString:(V2TIMMessage *)message {
return TIMCommonLocalizableString(TUIKitMessageTypeAnimateEmoji);
}
@end

View File

@@ -0,0 +1,72 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
#import <TIMCommon/TUIBubbleMessageCellData.h>
#import <TIMCommon/TUIMessageCellData.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIFileMessageCellData : TUIBubbleMessageCellData <TUIMessageCellDataFileUploadProtocol, TUIMessageCellDataFileDownloadProtocol>
/**
* File path
*/
@property(nonatomic, strong) NSString *path;
/**
* File name, including suffix.
*/
@property(nonatomic, strong) NSString *fileName;
/**
* Inner ID for file
*/
@property(nonatomic, strong) NSString *uuid;
/**
* File size, used to display the data volume information of the file.
*/
@property(nonatomic, assign) int length;
/**
* The progress of file uploading, which maintained by the cellData.
*/
@property(nonatomic, assign) NSUInteger uploadProgress;
/**
* The progress of file downloading, which maintained by the cellData.
*/
@property(nonatomic, assign) NSUInteger downladProgress;
/**
* The flag of indicating whether the file is downloading
* YES: dowloading; NO: not download
*/
@property(nonatomic, assign) BOOL isDownloading;
/**
* Downloading the file
* This method integrates and calls the IM SDK, and obtains the file through the interface provided by the SDK.
* 1. Before downloading the file from server, it will try to read file from local when the file exists in the local.
* 2. When the file not exists in the local, it will download from server through the api provided by IMSDK. But if there is downloading task, it will wait for
* the task finished.
* - The download progress (percentage value) is updated through the callback of the IMSDK.
* - There are two parameters which is @curSize and @totalSize in the callback of IMSDK. The progress value equals to curSize * 100 / totalSize.
* 3. When finished download, the file will be storaged to the @path.
*/
- (void)downloadFile;
/**
* Determine if the file is already downloaded to local
* This method will first try to get the file path from the local, if the acquisition is successful, record the path and return YES. Otherwise return NO.
*/
- (BOOL)isLocalExist;
/**
* Getting the file path and it will return the flag of whether the file exists through @isExist.
*/
- (NSString *)getFilePath:(BOOL *)isExist;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,148 @@
//
// TUIFileMessageCellData.m
// TXIMSDK_TUIKit_iOS
//
// Created by annidyfeng on 2019/5/21.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIFileMessageCellData.h"
#import <TIMCommon/TIMDefine.h>
#import <TUICore/NSString+TUIUtil.h>
#import "TUIMessageProgressManager.h"
@interface TUIFileMessageCellData ()
@property(nonatomic, strong) NSMutableArray *progressBlocks;
@property(nonatomic, strong) NSMutableArray *responseBlocks;
@end
@implementation TUIFileMessageCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
V2TIMFileElem *elem = message.fileElem;
TUIFileMessageCellData *fileData = [[TUIFileMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
fileData.path = [elem.path safePathString];
fileData.fileName = elem.filename;
fileData.length = elem.fileSize;
fileData.uuid = elem.uuid;
fileData.reuseId = TFileMessageCell_ReuseId;
return fileData;
}
+ (NSString *)getDisplayString:(V2TIMMessage *)message {
return TIMCommonLocalizableString(TUIkitMessageTypeFile); // @"[File]";
}
- (Class)getReplyQuoteViewDataClass {
return NSClassFromString(@"TUIFileReplyQuoteViewData");
}
- (Class)getReplyQuoteViewClass {
return NSClassFromString(@"TUIFileReplyQuoteView");
}
- (int)length {
if (self.innerMessage) {
_length = self.innerMessage.fileElem.fileSize;
}
return _length;
}
- (instancetype)initWithDirection:(TMsgDirection)direction {
self = [super initWithDirection:direction];
if (self) {
_uploadProgress = 100;
_downladProgress = 100;
_isDownloading = NO;
_progressBlocks = [NSMutableArray array];
_responseBlocks = [NSMutableArray array];
}
return self;
}
- (void)downloadFile {
BOOL isExist = NO;
NSString *path = [self getFilePath:&isExist];
if (isExist) {
return;
}
NSInteger progress = [TUIMessageProgressManager.shareManager downloadProgressForMessage:self.msgID];
if (progress != 0) {
return;
}
if (self.isDownloading) return;
self.isDownloading = YES;
@weakify(self);
if (self.innerMessage.elemType == V2TIM_ELEM_TYPE_FILE) {
NSString *msgID = self.msgID;
[self.innerMessage.fileElem downloadFile:path
progress:^(NSInteger curSize, NSInteger totalSize) {
@strongify(self);
NSInteger progress = curSize * 100 / totalSize;
[self updateDownalodProgress:MIN(progress, 99)];
[TUIMessageProgressManager.shareManager appendDownloadProgress:msgID progress:MIN(progress, 99)];
}
succ:^{
@strongify(self);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.isDownloading = NO;
[self updateDownalodProgress:100];
[TUIMessageProgressManager.shareManager appendDownloadProgress:msgID progress:100];
dispatch_async(dispatch_get_main_queue(), ^{
self.path = path;
});
});
}
fail:^(int code, NSString *msg) {
@strongify(self);
self.isDownloading = NO;
}];
}
}
- (void)updateDownalodProgress:(NSUInteger)progress {
dispatch_async(dispatch_get_main_queue(), ^{
self.downladProgress = progress;
});
}
- (BOOL)isLocalExist {
BOOL isExist;
[self getFilePath:&isExist];
return isExist;
}
- (NSString *)getFilePath:(BOOL *)isExist {
NSString *path = nil;
BOOL isDir = NO;
*isExist = NO;
if (self.direction == MsgDirectionOutgoing) {
// The origin file path is valid when uploading
path = [NSString stringWithFormat:@"%@%@", TUIKit_File_Path, _path.lastPathComponent];
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
if (!isDir) {
*isExist = YES;
}
}
}
if (!*isExist) {
path = [NSString stringWithFormat:@"%@%@%@", TUIKit_File_Path,self.uuid, _fileName];
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
if (!isDir) {
*isExist = YES;
}
}
}
if (*isExist) {
_path = path;
}
// TODO: uuid
return path;
}
@end

View File

@@ -0,0 +1,22 @@
//
// TUIGroupNoticeCellData.h
// TUIGroup
//
// Created by harvy on 2022/1/11.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIGroupNoticeCellData : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *desc;
@property(nonatomic, weak) id target;
@property(nonatomic, assign) SEL selector;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,13 @@
//
// TUIGroupNoticeCellData.m
// TUIGroup
//
// Created by harvy on 2022/1/11.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIGroupNoticeCellData.h"
@implementation TUIGroupNoticeCellData
@end

View File

@@ -0,0 +1,106 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
#import <TIMCommon/TUIBubbleMessageCellData.h>
#import "TUIChatDefine.h"
#import "TUIMessageItem.h"
NS_ASSUME_NONNULL_BEGIN
/////////////////////////////////////////////////////////////////////////////////
//
// TUIImageMessageCellData
//
/////////////////////////////////////////////////////////////////////////////////
/**
*
* 【Module name】 TUIImageMessageCellData
* 【Function description】It is used to realize the picture bubble in the chat window, including the display of picture message sending progress.
* At the same time, this module already supports three different types of "thumbnail", "large image" and "original image", and
* has handled the business logic of displaying the corresponding image type under appropriate circumstances:
* 1. Thumbnail - By default, you see thumbnails in the chat window, which is smaller and saves traffic.
* 2. Large image - If the user clicks on the thumbnail, they see a larger image with a better resolution.
* 3. Original image - If the sender chooses to send the original image, the recipient will see the "original image" button which can click to download the
* image with the original size.
*/
@interface TUIImageMessageCellData : TUIBubbleMessageCellData <TUIMessageCellDataFileUploadProtocol>
@property(nonatomic, strong) UIImage *thumbImage;
@property(nonatomic, strong) UIImage *originImage;
@property(nonatomic, strong) UIImage *largeImage;
/**
*
* The file storage path
*
* @note
* @path is maintained by the program by default, you can directly obtain the demo storage path by importing TIMDefine.h and referencing TUIKit_Image_Path
* Other routes are also available if you have further individual needs
*/
@property(nonatomic, strong) NSString *path;
@property(nonatomic, assign) NSInteger length;
/**
*
* The set of image items
*
* @note
* There are usually three imageItems stored in @items, namely thumb (thumb image), origin (original image), and large (large image), which is convenient to
* obtain images flexibly according to needs.
*
*/
@property(nonatomic, strong) NSMutableArray *items;
/**
* The progress of loading thumbnail
*/
@property(nonatomic, assign) NSUInteger thumbProgress;
/**
* The progress of loading origin image
*/
@property(nonatomic, assign) NSUInteger originProgress;
/**
* The progress of loading large image
*/
@property(nonatomic, assign) NSUInteger largeProgress;
/**
* The progress of uploading (sending)
*/
@property(nonatomic, assign) NSUInteger uploadProgress;
@property(nonatomic, assign) BOOL isSuperLongImage;
/**
* Downloading image.
* This method integrates and calls the IM SDK, and obtains images from sever through the interface provided by the SDK.
* 1. Before downloading the file from server, it will try to read file from local when the file exists in the local.
* 2. If the file is not exist in the local, it will download from server through the api named @getImage which provided by the class of TIMImage in the IMSDK.
* - The download progress (percentage value) is updated through the callback of the IMSDK.
* - There are two parameters which is @curSize and @totalSize in the callback of IMSDK. The progress value equals to curSize * 100 / totalSize.
* - The type of items in the image message is TIMElem. You can obtain image list from the paramter named imageList provided by TIMElem, which including
* original image、large image and thumbnail and you can obtain the image from it with the @imageType.
* 3. The image obtained through the SDK interface is a binary file, which needs to be decoded first, converted to CGIamge for decoding, and then packaged as a
* UIImage before it can be used.
* 4. When finished download, the image will be storaged to the @path.
*/
- (void)downloadImage:(TUIImageType)type;
- (void)downloadImage:(TUIImageType)type finish:(TUIImageMessageDownloadCallback)finish;
/**
*
* Decode the image and assign the image to a variable of the corresponding type (@thumbImage, @largeImage or @originImage).
*/
- (void)decodeImage:(TUIImageType)type;
/**
*
* Getting image file path
*/
- (NSString *)getImagePath:(TUIImageType)type isExist:(BOOL *)isExist;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,227 @@
//
// TUIImageMessageCellData.m
// TXIMSDK_TUIKit_iOS
//
// Created by annidyfeng on 2019/5/21.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIImageMessageCellData.h"
#import <TIMCommon/TIMDefine.h>
#import <TUICore/NSString+TUIUtil.h>
@interface TUIImageMessageCellData ()
@property(nonatomic, assign) BOOL isDownloading;
@property(nonatomic, copy) TUIImageMessageDownloadCallback onFinish;
@end
@implementation TUIImageMessageCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
V2TIMImageElem *elem = message.imageElem;
TUIImageMessageCellData *imageData = [[TUIImageMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
imageData.path = [elem.path safePathString];
imageData.items = [NSMutableArray array];
for (V2TIMImage *item in elem.imageList) {
TUIImageItem *itemData = [[TUIImageItem alloc] init];
itemData.uuid = item.uuid;
itemData.size = CGSizeMake(item.width, item.height);
// itemData.url = item.url;
if (item.type == V2TIM_IMAGE_TYPE_THUMB) {
itemData.type = TImage_Type_Thumb;
} else if (item.type == V2TIM_IMAGE_TYPE_LARGE) {
itemData.type = TImage_Type_Large;
} else if (item.type == V2TIM_IMAGE_TYPE_ORIGIN) {
itemData.type = TImage_Type_Origin;
}
[imageData.items addObject:itemData];
}
imageData.reuseId = TImageMessageCell_ReuseId;
return imageData;
}
+ (NSString *)getDisplayString:(V2TIMMessage *)message {
return TIMCommonLocalizableString(TUIkitMessageTypeImage); // @"[Image]";
}
- (Class)getReplyQuoteViewDataClass {
return NSClassFromString(@"TUIImageReplyQuoteViewData");
}
- (Class)getReplyQuoteViewClass {
return NSClassFromString(@"TUIImageReplyQuoteView");
}
- (instancetype)initWithDirection:(TMsgDirection)direction {
self = [super initWithDirection:direction];
if (self) {
_uploadProgress = 100;
if (direction == MsgDirectionIncoming) {
self.cellLayout = [TUIMessageCellLayout incommingImageMessageLayout];
} else {
self.cellLayout = [TUIMessageCellLayout outgoingImageMessageLayout];
}
}
return self;
}
- (NSString *)getImagePath:(TUIImageType)type isExist:(BOOL *)isExist {
NSString *path = nil;
BOOL isDir = NO;
*isExist = NO;
if (self.direction == MsgDirectionOutgoing) {
path = [NSString stringWithFormat:@"%@%@", TUIKit_Image_Path, _path.lastPathComponent];
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
if (!isDir) {
*isExist = YES;
}
}
}
if (!*isExist) {
TUIImageItem *tImageItem = [self getTImageItem:type];
path = [NSString stringWithFormat:@"%@%@_%ld", TUIKit_Image_Path, tImageItem.uuid, (long)tImageItem.type];
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
if (!isDir) {
*isExist = YES;
}
}
}
return path;
}
- (void)downloadImage:(TUIImageType)type finish:(TUIImageMessageDownloadCallback)finish {
self.onFinish = finish;
[self downloadImage:type];
}
- (void)downloadImage:(TUIImageType)type {
BOOL isExist = NO;
NSString *path = [self getImagePath:type isExist:&isExist];
if (isExist) {
[self decodeImage:type];
return;
}
if (self.isDownloading) {
return;
}
self.isDownloading = YES;
V2TIMImage *imImage = [self getIMImage:type];
@weakify(self);
[imImage downloadImage:path
progress:^(NSInteger curSize, NSInteger totalSize) {
@strongify(self);
NSInteger progress = curSize * 100 / totalSize;
[self updateProgress:MIN(progress, 99) withType:type];
}
succ:^{
@strongify(self);
self.isDownloading = NO;
[self updateProgress:100 withType:type];
[self decodeImage:type];
}
fail:^(int code, NSString *msg) {
@strongify(self);
self.isDownloading = NO;
/**
* If the uuid of the picture is the same (the same user sends
* the same picture continuously), the same path may trigger multiple download operations. Except for the first time, subsequent downloads will report
* an error. At this time, it is necessary to judge whether the local file exists.
*/
[self decodeImage:type];
}];
}
- (void)updateProgress:(NSUInteger)progress withType:(TUIImageType)type {
dispatch_async(dispatch_get_main_queue(), ^{
if (type == TImage_Type_Thumb) self.thumbProgress = progress;
if (type == TImage_Type_Large) self.largeProgress = progress;
if (type == TImage_Type_Origin) self.originProgress = progress;
});
}
- (void)decodeImage:(TUIImageType)type {
BOOL isExist = NO;
NSString *path = [self getImagePath:type isExist:&isExist];
if (!isExist) {
return;
}
__weak typeof(self) weakSelf = self;
void (^finishBlock)(UIImage *) = ^(UIImage *image) {
if (type == TImage_Type_Thumb) {
weakSelf.thumbImage = image;
weakSelf.thumbProgress = 100;
weakSelf.uploadProgress = 100;
}
if (type == TImage_Type_Large) {
weakSelf.largeImage = image;
weakSelf.largeProgress = 100;
}
if (type == TImage_Type_Origin) {
weakSelf.originImage = image;
weakSelf.originProgress = 100;
}
if (weakSelf.onFinish) {
weakSelf.onFinish();
}
};
NSString *cacheKey = [path substringFromIndex:TUIKit_Image_Path.length];
UIImage *cacheImage = [[SDImageCache sharedImageCache] imageFromCacheForKey:cacheKey];
if (cacheImage) {
finishBlock(cacheImage);
} else {
[TUITool asyncDecodeImage:path
complete:^(NSString *path, UIImage *image) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"image.sd_imageFormat: %ld path:%@ image.sd_imageData.length :%lu",(long)image.sd_imageFormat,path,(unsigned long)image.sd_imageData.length);
if (![path tui_containsString:@".gif"] || (image.sd_imageFormat != SDImageFormatGIF) ) {
[[SDImageCache sharedImageCache] storeImageToMemory:image forKey:cacheKey];
}
else {
/**
* The gif image is too large to be cached in memory
* Only cache images less than 1M
*/
if (image.sd_imageData.length < 1 * 1024 * 1024) {
[[SDImageCache sharedImageCache] storeImageToMemory:image forKey:cacheKey];
}
}
finishBlock(image);
});
}];
}
}
- (TUIImageItem *)getTImageItem:(TUIImageType)type {
for (TUIImageItem *item in self.items) {
if (item.type == type) {
return item;
}
}
return nil;
}
- (V2TIMImage *)getIMImage:(TUIImageType)type {
V2TIMMessage *imMsg = self.innerMessage;
if (imMsg.elemType == V2TIM_ELEM_TYPE_IMAGE) {
for (V2TIMImage *imImage in imMsg.imageElem.imageList) {
if (type == TImage_Type_Thumb && imImage.type == V2TIM_IMAGE_TYPE_THUMB) {
return imImage;
} else if (type == TImage_Type_Origin && imImage.type == V2TIM_IMAGE_TYPE_ORIGIN) {
return imImage;
} else if (type == TImage_Type_Large && imImage.type == V2TIM_IMAGE_TYPE_LARGE) {
return imImage;
}
}
}
return nil;
}
@end

View File

@@ -0,0 +1,41 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
/**
*
*
* This file declares the TUIJoinGroupMessageCellData class.
* This document is responsible for realizing the function of the small gray bar for entering the group, and can also be further extended to a group message
* unit with a single operator. That is, this file can highlight the operator's nickname in blue and provide a response interface for the highlighted part in
* blue.
*/
#import <TIMCommon/TUISystemMessageCellData.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIJoinGroupMessageCellData : TUISystemMessageCellData
/**
*
* Operator nickname. For example, "Tom joined the group", the variable content is "Tom"
*/
@property(nonatomic, strong) NSString *opUserName;
/**
* The nickname of the operator.
*/
@property(nonatomic, strong) NSMutableArray<NSString *> *userNameList;
/**
* Operator Id.
*/
@property(nonatomic, strong) NSString *opUserID;
/**
* List of the operator IDs.
*/
@property(nonatomic, strong) NSMutableArray<NSString *> *userIDList;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,17 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
#import "TUIJoinGroupMessageCellData.h"
@implementation TUIJoinGroupMessageCellData
- (instancetype)initWithDirection:(TMsgDirection)direction {
self = [super initWithDirection:direction];
if (self) {
self.userNameList = [NSMutableArray array];
self.userIDList = [NSMutableArray array];
}
return self;
}
@end

View File

@@ -0,0 +1,25 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/////////////////////////////////////////////////////////////////////////////////
//
// TUIMenuCellData
//
/////////////////////////////////////////////////////////////////////////////////
@interface TUIMenuCellData : NSObject
/**
* Access path for grouped thumbnails in grouping units
*/
@property(nonatomic, strong) NSString *path;
@property(nonatomic, assign) BOOL isSelected;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,9 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
#import "TUIMenuCellData.h"
@implementation TUIMenuCellData
@end

View File

@@ -0,0 +1,28 @@
//
// TUIMergeMessageCellData.h
// Pods
//
// Created by harvy on 2020/12/9.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <TIMCommon/TIMDefine.h>
#import <TIMCommon/TUIBubbleMessageCellData.h>
#import <TIMCommon/TUIMessageCellData.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIMergeMessageCellData : TUIMessageCellData
@property(nonatomic, copy) NSString *title;
@property(nonatomic, strong) NSArray<NSString *> *abstractList;
@property(nonatomic, strong) V2TIMMergerElem *mergerElem;
@property(nonatomic, assign) CGSize abstractSize;
@property(nonatomic, assign) CGSize abstractRow1Size;
@property(nonatomic, assign) CGSize abstractRow2Size;
@property(nonatomic, assign) CGSize abstractRow3Size;
@property(nonatomic, strong) NSArray<NSDictionary *> *abstractSendDetailList;
- (NSAttributedString *)abstractAttributedString;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,119 @@
//
// TUIMergeMessageCellData.m
// Pods
//
// Created by harvy on 2020/12/9.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIMergeMessageCellData.h"
#import <TIMCommon/TIMDefine.h>
#import "TUITextMessageCellData.h"
#import <TIMCommon/NSString+TUIEmoji.h>
@implementation TUIMergeMessageCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
V2TIMMergerElem *elem = message.mergerElem;
if (elem.layersOverLimit) {
TUITextMessageCellData *limitCell = [[TUITextMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
limitCell.content = TIMCommonLocalizableString(TUIKitRelayLayerLimitTips);
return limitCell;
}
TUIMergeMessageCellData *mergeData = [[TUIMergeMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
mergeData.title = elem.title;
mergeData.abstractList = [NSArray arrayWithArray:elem.abstractList];
mergeData.abstractSendDetailList = [self.class formatAbstractSendDetailList:elem.abstractList];
mergeData.mergerElem = elem;
mergeData.reuseId = TMergeMessageCell_ReuserId;
return mergeData;
}
+ (NSString *)getDisplayString:(V2TIMMessage *)message {
return [NSString stringWithFormat:@"[%@]", TIMCommonLocalizableString(TUIKitRelayChatHistory)];
}
- (Class)getReplyQuoteViewDataClass {
return NSClassFromString(@"TUIMergeReplyQuoteViewData");
}
- (Class)getReplyQuoteViewClass {
return NSClassFromString(@"TUIMergeReplyQuoteView");
}
- (NSAttributedString *)abstractAttributedString {
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.lineSpacing = 4;
style.alignment = isRTL()? NSTextAlignmentRight:NSTextAlignmentLeft;
NSDictionary *attribute = @{
NSForegroundColorAttributeName : [UIColor colorWithRed:187 / 255.0 green:187 / 255.0 blue:187 / 255.0 alpha:1 / 1.0],
NSFontAttributeName : [UIFont systemFontOfSize:12.0],
NSParagraphStyleAttributeName : style
};
NSMutableAttributedString *abstr = [[NSMutableAttributedString alloc] initWithString:@""];
int i = 0;
for (NSString *ab in self.abstractList) {
if (i >= 4) {
break;
}
NSString *resultStr = [NSString stringWithFormat:@"%@\n", ab];
NSString *str = resultStr;
[abstr appendAttributedString:[[NSAttributedString alloc] initWithString:str attributes:attribute]];
i++;
}
return abstr;
}
+ (NSMutableArray *)formatAbstractSendDetailList:(NSArray *)originAbstractList {
NSMutableArray *array = [NSMutableArray arrayWithCapacity:3];
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.alignment = isRTL()? NSTextAlignmentRight:NSTextAlignmentLeft;
style.lineBreakMode = NSLineBreakByTruncatingTail;
NSDictionary *attribute = @{
NSForegroundColorAttributeName : [UIColor colorWithRed:187 / 255.0 green:187 / 255.0 blue:187 / 255.0 alpha:1 / 1.0],
NSFontAttributeName : [UIFont systemFontOfSize:12.0],
NSParagraphStyleAttributeName : style
};
int i = 0;
for (NSString *ab in originAbstractList) {
if (i >= 4) {
break;
}
NSString *str = ab;
NSString * splitStr = @":";
if ([str tui_containsString:@"\u202C:"]) {
splitStr = @"\u202C:";
}
NSArray<NSString *> *result = [str componentsSeparatedByString:splitStr];
NSString *sender = result[0];
NSString *detail = result[1];
sender = [NSString stringWithFormat:@"%@",sender];
detail = [NSString stringWithFormat:@"%@",detail.getLocalizableStringWithFaceContent];
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithCapacity:3];
if(sender.length>0 ){
NSMutableAttributedString *abstr = [[NSMutableAttributedString alloc] initWithString:@""];
[abstr appendAttributedString:[[NSAttributedString alloc] initWithString:sender attributes:attribute]];
[dic setObject:abstr forKey:@"sender"];
}
if(detail.length>0 ){
NSMutableAttributedString *abstr = [[NSMutableAttributedString alloc] initWithString:@""];
[abstr appendAttributedString:[[NSAttributedString alloc] initWithString:detail attributes:attribute]];
[dic setObject:abstr forKey:@"detail"];
}
[array addObject:dic];
i++;
}
return array;
}
- (BOOL)isArString:(NSString *)text {
NSString *isoLangCode = (__bridge_transfer NSString *)CFStringTokenizerCopyBestStringLanguage((__bridge CFStringRef)text, CFRangeMake(0, text.length));
if ([isoLangCode isEqualToString:@"ar"]) {
return YES;
}
return NO;
}
@end

View File

@@ -0,0 +1,19 @@
//
// TUITypingStatusCellData.h
// TUIChat
//
// Created by wyl on 2022/7/4.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <TIMCommon/TUIMessageCellData.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUITypingStatusCellData : TUIMessageCellData
@property(nonatomic, assign) NSInteger typingStatus;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,25 @@
//
// TUITypingStatusCellData.m
// TUIChat
//
// Created by wyl on 2022/7/4.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUITypingStatusCellData.h"
@implementation TUITypingStatusCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data options:NSJSONReadingAllowFragments error:nil];
TUITypingStatusCellData *cellData = [[TUITypingStatusCellData alloc] initWithDirection:message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming];
cellData.msgID = message.msgID;
if ([param.allKeys containsObject:@"typingStatus"]) {
cellData.typingStatus = [param[@"typingStatus"] intValue];
}
return cellData;
}
@end

View File

@@ -0,0 +1,60 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
/**
*
* 1. This file declares the TUIVideoItem class, TUISnapshotItem class, and TUIVideoMessageCellData class.
* - TUIVideoItem corresponds to V2TIMVideoElem in the IM SDK. We convert the classes in the SDK to TUIVideoItem, which is convenient for us to further
* process and operate the data.
* - TUISnapshotItem corresponds to the video cover class in the IM SDK. It is still an image in essence, but is bound to the corresponding Video.
* - TUIVideoMessageCellData inherits from the TUIMessageCellData class and is used to store a series of data and information required by the image message
* unit.
* 2. The business logic for obtaining video information and cover information has been implemented in this document. When you need to get video and cover
* data, you can directly call the relevant member functions declared in this file
*/
#import <TIMCommon/TUIBubbleMessageCellData.h>
#import "TUIChatDefine.h"
#import "TUIMessageItem.h"
NS_ASSUME_NONNULL_BEGIN
/////////////////////////////////////////////////////////////////////////////////
//
// TUIVideoMessageCellData
//
/////////////////////////////////////////////////////////////////////////////////
@interface TUIVideoMessageCellData : TUIBubbleMessageCellData <TUIMessageCellDataFileUploadProtocol>
@property(nonatomic, strong) UIImage *thumbImage;
@property(nonatomic, strong) NSString *videoPath;
@property(nonatomic, strong) NSString *snapshotPath;
@property(nonatomic, strong) TUIVideoItem *videoItem;
@property(nonatomic, strong) TUISnapshotItem *snapshotItem;
@property(nonatomic, assign) NSUInteger uploadProgress;
@property(nonatomic, assign) NSUInteger thumbProgress;
@property(nonatomic, assign) NSUInteger videoProgress;
/// Is the current message a custom message
@property(nonatomic, assign) BOOL isPlaceHolderCellData;
+ (TUIMessageCellData *)placeholderCellDataWithSnapshotUrl:(NSString *)snapshotUrl thubImage:(UIImage *)thubImage;
- (void)getVideoUrl:(void (^)(NSString *url))urlCallBack;
/**
* Downloading the cover image of the video. It will download from server if the image not exist in local.
*/
- (void)downloadThumb;
- (void)downloadThumb:(TUIVideoMessageDownloadCallback)finish;
/**
* Downloading the video file. It will download from server if the video not exist in local.
*/
- (void)downloadVideo;
- (BOOL)isVideoExist;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,290 @@
//
// TUIVideoMessageCellData.m
// TXIMSDK_TUIKit_iOS
//
// Created by annidyfeng on 2019/5/21.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIVideoMessageCellData.h"
#import <TIMCommon/TIMDefine.h>
#import <TUICore/NSString+TUIUtil.h>
#import <TUICore/TUILogin.h>
#define TVideo_Block_Progress @"TVideo_Block_Progress";
#define TVideo_Block_Response @"TVideo_Block_Response";
@interface TUIVideoMessageCellData ()
@property(nonatomic, strong) NSString *videoUrl;
@property(nonatomic, assign) BOOL isDownloadingSnapshot;
@property(nonatomic, assign) BOOL isDownloadingVideo;
@property(nonatomic, copy) TUIVideoMessageDownloadCallback onFinish;
@end
@implementation TUIVideoMessageCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
V2TIMVideoElem *elem = message.videoElem;
TUIVideoMessageCellData *videoData = [[TUIVideoMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
videoData.videoPath = [elem.videoPath safePathString];
videoData.snapshotPath = [elem.snapshotPath safePathString];
videoData.videoItem = [[TUIVideoItem alloc] init];
videoData.videoItem.uuid = elem.videoUUID;
videoData.videoItem.type = elem.videoType;
videoData.videoItem.length = elem.videoSize;
videoData.videoItem.duration = elem.duration;
videoData.snapshotItem = [[TUISnapshotItem alloc] init];
videoData.snapshotItem.uuid = elem.snapshotUUID;
// videoData.snapshotItem.type = elem.snaps;
videoData.snapshotItem.length = elem.snapshotSize;
videoData.snapshotItem.size = CGSizeMake(elem.snapshotWidth, elem.snapshotHeight);
videoData.reuseId = TVideoMessageCell_ReuseId;
return videoData;
}
+ (TUIMessageCellData *)placeholderCellDataWithSnapshotUrl:(NSString *)snapshotUrl thubImage:(UIImage *)thubImage {
TUIVideoMessageCellData *videoData = [[TUIVideoMessageCellData alloc] initWithDirection:(MsgDirectionOutgoing)];
videoData.thumbImage = thubImage;
videoData.snapshotPath = [snapshotUrl safePathString];
videoData.videoItem = [[TUIVideoItem alloc] init];
videoData.snapshotItem = [[TUISnapshotItem alloc] init];
videoData.snapshotItem.size = CGSizeEqualToSize(thubImage.size, CGSizeZero) ? CGSizeMake(kScale375(100), kScale375(100)) : thubImage.size;
videoData.reuseId = TVideoMessageCell_ReuseId;
videoData.avatarUrl = [NSURL URLWithString:[TUILogin getFaceUrl]];
videoData.isPlaceHolderCellData = YES;
return videoData;
}
+ (NSString *)getDisplayString:(V2TIMMessage *)message {
return TIMCommonLocalizableString(TUIkitMessageTypeVideo);
}
- (Class)getReplyQuoteViewDataClass {
return NSClassFromString(@"TUIVideoReplyQuoteViewData");
}
- (Class)getReplyQuoteViewClass {
return NSClassFromString(@"TUIVideoReplyQuoteView");
}
- (instancetype)initWithDirection:(TMsgDirection)direction {
self = [super initWithDirection:direction];
if (self) {
_uploadProgress = 100;
_isDownloadingVideo = NO;
_isDownloadingSnapshot = NO;
if (direction == MsgDirectionIncoming) {
self.cellLayout = [TUIMessageCellLayout incommingVideoMessageLayout];
} else {
self.cellLayout = [TUIMessageCellLayout outgoingVideoMessageLayout];
}
}
return self;
}
- (void)downloadThumb:(TUIVideoMessageDownloadCallback)finish {
self.onFinish = finish;
[self downloadThumb];
}
- (void)downloadThumb {
BOOL isExist = NO;
NSString *path = [self getSnapshotPath:&isExist];
if (isExist) {
[self decodeThumb];
return;
}
if (self.isDownloadingSnapshot) {
return;
}
self.isDownloadingSnapshot = YES;
@weakify(self);
V2TIMMessage *imMsg = self.innerMessage;
if (imMsg.elemType == V2TIM_ELEM_TYPE_VIDEO) {
// Avoid large files that slow down callback progress.
[self updateThumbProgress:1];
[imMsg.videoElem downloadSnapshot:path
progress:^(NSInteger curSize, NSInteger totalSize) {
[self updateThumbProgress:MAX(1, curSize * 100 / totalSize)];
}
succ:^{
@strongify(self);
self.isDownloadingSnapshot = NO;
[self updateThumbProgress:100];
[self decodeThumb];
}
fail:^(int code, NSString *msg) {
@strongify(self);
self.isDownloadingSnapshot = NO;
}];
}
}
- (void)updateThumbProgress:(NSUInteger)progress {
dispatch_async(dispatch_get_main_queue(), ^{
self.thumbProgress = progress;
});
}
- (void)decodeThumb {
BOOL isExist = NO;
NSString *path = [self getSnapshotPath:&isExist];
if (!isExist) {
return;
}
@weakify(self);
[TUITool asyncDecodeImage:path
complete:^(NSString *path, UIImage *image) {
@strongify(self);
@weakify(self);
dispatch_async(dispatch_get_main_queue(), ^{
@strongify(self);
self.thumbImage = image;
self.thumbProgress = 100;
if (self.onFinish) {
self.onFinish();
}
});
}];
}
- (void)downloadVideo {
BOOL isExist = NO;
NSString *path = [self getVideoPath:&isExist];
if (isExist) {
return;
}
if (self.isDownloadingVideo) {
return;
}
self.isDownloadingVideo = YES;
@weakify(self);
V2TIMMessage *imMsg = self.innerMessage;
if (imMsg.elemType == V2TIM_ELEM_TYPE_VIDEO) {
[imMsg.videoElem downloadVideo:path
progress:^(NSInteger curSize, NSInteger totalSize) {
@strongify(self);
[self updateVideoProgress:curSize * 100 / totalSize];
}
succ:^{
@strongify(self);
self.isDownloadingVideo = NO;
[self updateVideoProgress:100];
dispatch_async(dispatch_get_main_queue(), ^{
self.videoPath = path;
});
}
fail:^(int code, NSString *msg) {
@strongify(self);
self.isDownloadingVideo = NO;
}];
}
}
- (void)updateVideoProgress:(NSUInteger)progress {
dispatch_async(dispatch_get_main_queue(), ^{
self.videoProgress = progress;
});
}
- (void)getVideoUrl:(void (^)(NSString *url))urlCallBack {
if (!urlCallBack) {
return;
}
if (self.videoUrl) {
urlCallBack(self.videoUrl);
}
@weakify(self);
V2TIMMessage *imMsg = self.innerMessage;
if (imMsg.elemType == V2TIM_ELEM_TYPE_VIDEO) {
[imMsg.videoElem getVideoUrl:^(NSString *url) {
@strongify(self);
self.videoUrl = url;
urlCallBack(self.videoUrl);
}];
}
}
- (BOOL)isVideoExist {
BOOL isExist;
[self getVideoPath:&isExist];
return isExist;
}
- (NSString *)getVideoPath:(BOOL *)isExist {
NSString *path = nil;
BOOL isDir = NO;
*isExist = NO;
if (_videoPath && _videoPath.lastPathComponent.length) {
path = _videoPath;
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
if (!isDir) {
*isExist = YES;
}
}
else {
path = [NSString stringWithFormat:@"%@%@", TUIKit_Video_Path, _videoPath.lastPathComponent];
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
if (!isDir) {
*isExist = YES;
}
}
}
}
if (!*isExist) {
if (_videoItem) {
if (_videoItem.uuid && _videoItem.uuid.length && _videoItem.type && _videoItem.type.length) {
path = [NSString stringWithFormat:@"%@%@.%@", TUIKit_Video_Path, _videoItem.uuid, _videoItem.type];
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
if (!isDir) {
*isExist = YES;
}
}
}
}
}
if (*isExist) {
_videoPath = path;
}
return path;
}
- (NSString *)getSnapshotPath:(BOOL *)isExist {
NSString *path = nil;
BOOL isDir = NO;
*isExist = NO;
if (_snapshotPath && _snapshotPath.length) {
path = [NSString stringWithFormat:@"%@%@", TUIKit_Video_Path, _snapshotPath.lastPathComponent];
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
if (!isDir) {
*isExist = YES;
}
}
}
if (!*isExist) {
if (_snapshotItem) {
if (_snapshotItem.uuid && _snapshotItem.uuid.length) {
path = [NSString stringWithFormat:@"%@%@", TUIKit_Video_Path, _snapshotItem.uuid];
path = [TUIKit_Video_Path stringByAppendingString:_snapshotItem.uuid];
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
if (!isDir) {
*isExist = YES;
}
}
}
}
}
return path;
}
@end

View File

@@ -0,0 +1,66 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
#import <TIMCommon/TUIBubbleMessageCellData.h>
#import <TIMCommon/TUIMessageCellData.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, TUIVoiceAudioPlaybackStyle) {
TUIVoiceAudioPlaybackStyleLoudspeaker = 1,
TUIVoiceAudioPlaybackStyleHandset = 2,
};
@interface TUIVoiceMessageCellData : TUIBubbleMessageCellData
@property(nonatomic, strong) NSString *path;
@property(nonatomic, strong) NSString *uuid;
@property(nonatomic, assign) int duration;
@property(nonatomic, assign) int length;
@property(nonatomic, assign) BOOL isDownloading;
@property(nonatomic, assign) BOOL isPlaying;
@property(nonatomic, assign) CGFloat voiceHeight;
@property(nonatomic, assign) NSTimeInterval currentTime;
/**
*
* Play animation image
* An animation used to implement the "sonic image" fade of the speech as it plays.
* If you want to customize the implementation of other kinds of animation icons, you can refer to the implementation of voiceAnimationIamges here.
*/
@property NSArray<UIImage *> *voiceAnimationImages;
/**
*
* voice icon
* Animated icon to show when the speech is not playing.
*/
@property UIImage *voiceImage;
@property(nonatomic, assign) CGFloat voiceTop;
/**
*
* Top margin of voice message
* This value is used to determine the position of the bubble, which is convenient for UI layout of the content in the bubble.
* If the value is abnormal or set arbitrarily, UI errors such as message position dislocation will occur.
*/
@property(nonatomic, class) CGFloat incommingVoiceTop;
@property(nonatomic, class) CGFloat outgoingVoiceTop;
- (void)stopVoiceMessage;
/**
* Begin to play voice. It will download the voice file from server if it not exists in local.
*/
- (void)playVoiceMessage;
@property(nonatomic, copy) void (^audioPlayerDidFinishPlayingBlock)(void);
//The style of audio playback.
+ (TUIVoiceAudioPlaybackStyle)getAudioplaybackStyle;
+ (void)changeAudioPlaybackStyle;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,263 @@
//
// TUIVoiceMessageCellData.m
// TXIMSDK_TUIKit_iOS
//
// Created by annidyfeng on 2019/5/21.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIVoiceMessageCellData.h"
#import <TIMCommon/TIMDefine.h>
#import <TUICore/TUIThemeManager.h>
@import AVFoundation;
@interface TUIVoiceMessageCellData () <AVAudioPlayerDelegate>
@property AVAudioPlayer *audioPlayer;
@property NSString *wavPath;
@property(nonatomic, strong) NSTimer *timer;
@end
@implementation TUIVoiceMessageCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
V2TIMSoundElem *elem = message.soundElem;
TUIVoiceMessageCellData *soundData = [[TUIVoiceMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
soundData.duration = elem.duration;
soundData.length = elem.dataSize;
soundData.uuid = elem.uuid;
soundData.reuseId = TVoiceMessageCell_ReuseId;
soundData.path = elem.path;
return soundData;
}
+ (NSString *)getDisplayString:(V2TIMMessage *)message {
return TIMCommonLocalizableString(TUIKitMessageTypeVoice); // @"[Voice]";
}
- (Class)getReplyQuoteViewDataClass {
return NSClassFromString(@"TUIVoiceReplyQuoteViewData");
}
- (Class)getReplyQuoteViewClass {
return NSClassFromString(@"TUIVoiceReplyQuoteView");
}
- (instancetype)initWithDirection:(TMsgDirection)direction {
self = [super initWithDirection:direction];
if (self) {
if (direction == MsgDirectionIncoming) {
self.cellLayout = [TUIMessageCellLayout incommingVoiceMessageLayout];
_voiceImage = TUIChatDynamicImage(@"chat_voice_message_receiver_voice_normal_img",
[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"message_voice_receiver_normal")]);
_voiceImage = [_voiceImage rtl_imageFlippedForRightToLeftLayoutDirection];
_voiceAnimationImages = [NSArray arrayWithObjects:[self.class formatImageByName:@"message_voice_receiver_playing_1"],
[self.class formatImageByName:@"message_voice_receiver_playing_2"],
[self.class formatImageByName:@"message_voice_receiver_playing_3"], nil];
_voiceTop = [[self class] incommingVoiceTop];
} else {
self.cellLayout = [TUIMessageCellLayout outgoingVoiceMessageLayout];
_voiceImage = TUIChatDynamicImage(@"chat_voice_message_sender_voice_normal_img",
[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"message_voice_sender_normal")]);
_voiceImage = [_voiceImage rtl_imageFlippedForRightToLeftLayoutDirection];
_voiceAnimationImages = [NSArray arrayWithObjects:[self.class formatImageByName:@"message_voice_sender_playing_1"],
[self.class formatImageByName:@"message_voice_sender_playing_2"],
[self.class formatImageByName:@"message_voice_sender_playing_3"], nil];
_voiceTop = [[self class] outgoingVoiceTop];
}
_voiceHeight = 21;
}
return self;
}
+ (UIImage *)formatImageByName:(NSString *)imgName {
NSString *path = TUIChatImagePath(imgName);
UIImage *img = [[TUIImageCache sharedInstance] getResourceFromCache:path];
return [img rtl_imageFlippedForRightToLeftLayoutDirection];
}
- (NSString *)getVoicePath:(BOOL *)isExist {
NSString *path = nil;
BOOL isDir = false;
*isExist = NO;
if (self.direction == MsgDirectionOutgoing) {
if (_path.length) {
path = [NSString stringWithFormat:@"%@%@", TUIKit_Voice_Path, _path.lastPathComponent];
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
if (!isDir) {
*isExist = YES;
}
}
}
}
if (!*isExist) {
if (_uuid.length) {
path = [NSString stringWithFormat:@"%@%@.amr", TUIKit_Voice_Path, _uuid];
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
if (!isDir) {
*isExist = YES;
}
}
}
}
return path;
}
- (V2TIMSoundElem *)getIMSoundElem {
V2TIMMessage *imMsg = self.innerMessage;
if (imMsg.elemType == V2TIM_ELEM_TYPE_SOUND) {
return imMsg.soundElem;
}
return nil;
}
- (void)playVoiceMessage {
if (self.isPlaying) {
[self stopVoiceMessage];
return;
}
self.isPlaying = YES;
if (self.innerMessage.localCustomInt == 0) self.innerMessage.localCustomInt = 1;
V2TIMSoundElem *imSound = [self getIMSoundElem];
BOOL isExist = NO;
if (self.uuid.length == 0) {
self.uuid = imSound.uuid;
}
NSString *path = [self getVoicePath:&isExist];
if (isExist) {
[self playInternal:path];
} else {
if (self.isDownloading) {
return;
}
//
self.isDownloading = YES;
@weakify(self);
[imSound downloadSound:path
progress:^(NSInteger curSize, NSInteger totalSize) {
}
succ:^{
@strongify(self);
self.isDownloading = NO;
[self playInternal:path];
}
fail:^(int code, NSString *msg) {
@strongify(self);
self.isDownloading = NO;
[self stopVoiceMessage];
}];
}
}
- (void)playInternal:(NSString *)path {
if (!self.isPlaying) return;
// play current
TUIVoiceAudioPlaybackStyle playbackStyle = [self.class getAudioplaybackStyle];
if(playbackStyle == TUIVoiceAudioPlaybackStyleHandset) {
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
}
else {
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
}
NSURL *url = [NSURL fileURLWithPath:path];
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
self.audioPlayer.delegate = self;
bool result = [self.audioPlayer play];
if (!result) {
self.wavPath = [[path stringByDeletingPathExtension] stringByAppendingString:@".wav"];
NSURL *url = [NSURL fileURLWithPath:self.wavPath];
[self.audioPlayer stop];
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
self.audioPlayer.delegate = self;
[self.audioPlayer play];
}
@weakify(self);
if (@available(iOS 10.0, *)) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1
repeats:YES
block:^(NSTimer *_Nonnull timer) {
@strongify(self);
[self updateProgress];
}];
} else {
// Fallback on earlier versions
}
}
//The style of audio playback.
+ (TUIVoiceAudioPlaybackStyle)getAudioplaybackStyle {
NSString *style = [NSUserDefaults.standardUserDefaults objectForKey:@"tui_audioPlaybackStyle"];
if ([style isEqualToString:@"1"]) {
return TUIVoiceAudioPlaybackStyleLoudspeaker;
} else if ([style isEqualToString:@"2"]) {
return TUIVoiceAudioPlaybackStyleHandset;
}
return TUIVoiceAudioPlaybackStyleLoudspeaker;
}
+ (void)changeAudioPlaybackStyle {
TUIVoiceAudioPlaybackStyle style = [self getAudioplaybackStyle];
if (style == TUIVoiceAudioPlaybackStyleLoudspeaker) {
[NSUserDefaults.standardUserDefaults setObject:@"2" forKey:@"tui_audioPlaybackStyle"];
}
else {
[NSUserDefaults.standardUserDefaults setObject:@"1" forKey:@"tui_audioPlaybackStyle"];
}
[NSUserDefaults.standardUserDefaults synchronize];
}
- (void)updateProgress {
@weakify(self);
dispatch_async(dispatch_get_main_queue(), ^{
@strongify(self);
self.currentTime = self.audioPlayer.currentTime;
});
}
- (void)stopVoiceMessage {
if ([self.audioPlayer isPlaying]) {
[self.audioPlayer stop];
self.audioPlayer = nil;
}
if (self.timer) {
[self.timer invalidate];
self.timer = nil;
}
self.isPlaying = NO;
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;
{
[self stopVoiceMessage];
[[NSFileManager defaultManager] removeItemAtPath:self.wavPath error:nil];
if (self.audioPlayerDidFinishPlayingBlock) {
self.audioPlayerDidFinishPlayingBlock();
}
}
static CGFloat gIncommingVoiceTop = 12;
+ (void)setIncommingVoiceTop:(CGFloat)incommingVoiceTop {
gIncommingVoiceTop = incommingVoiceTop;
}
+ (CGFloat)incommingVoiceTop {
return gIncommingVoiceTop;
}
static CGFloat gOutgoingVoiceTop = 12;
+ (void)setOutgoingVoiceTop:(CGFloat)outgoingVoiceTop {
gOutgoingVoiceTop = outgoingVoiceTop;
}
+ (CGFloat)outgoingVoiceTop {
return gOutgoingVoiceTop;
}
@end