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,16 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
/**
*
* This file declares the data source for TUIGroupCreatedCell
*/
#import <TIMCommon/TUISystemMessageCellData.h>
@interface TUIGroupCreatedCellData : TUISystemMessageCellData
@property(nonatomic, copy) NSString *opUser;
@property(nonatomic, strong) NSNumber *cmd;
@end

View File

@@ -0,0 +1,87 @@
//
// TUIGroupCreatedCellData.m
// TUIKitDemo
//
// Created by annidyfeng on 2019/6/10.
// Copyright © 2019 Tencent. All rights reserved.
//
#import "TUIGroupCreatedCellData.h"
#import <TUICore/NSString+TUIUtil.h>
@implementation TUIGroupCreatedCellData
+ (TUIGroupCreatedCellData *)getCellData:(V2TIMMessage *)message {
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data options:NSJSONReadingAllowFragments error:nil];
TUIGroupCreatedCellData *cellData = [[TUIGroupCreatedCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
cellData.innerMessage = message;
cellData.msgID = message.msgID;
cellData.content = param[@"content"];
cellData.opUser = [self.class getOpUserName:message]?:param[@"opUser"];
cellData.cmd = param[@"cmd"];
return cellData;
}
+ (NSString *)getOpUserName:(V2TIMMessage *)info {
NSString *opUser;
if (info.nameCard.length > 0) {
opUser = info.nameCard;
} else if (info.nickName.length > 0) {
opUser = info.nickName;
} else {
opUser = info.userID;
}
return opUser;
}
- (NSMutableAttributedString *)attributedString {
NSString *localizableContent = self.content;
if (self.cmd && [self.cmd isKindOfClass:NSNumber.class]) {
NSInteger command = [self.cmd integerValue];
if (command == 1) {
localizableContent = TIMCommonLocalizableString(TUICommunityCreateTipsMessage);
} else {
localizableContent = TIMCommonLocalizableString(TUIGroupCreateTipsMessage);
}
}
NSString *str = [NSString stringWithFormat:@"\"%@\" %@", self.opUser, localizableContent];
str = rtlString(str);
NSMutableAttributedString *attributeString = [[NSMutableAttributedString alloc] initWithString:str];
NSDictionary *attributeDict = @{NSForegroundColorAttributeName : [UIColor d_systemGrayColor]};
[attributeString setAttributes:attributeDict range:NSMakeRange(0, attributeString.length)];
return attributeString;
}
+ (NSString *)getDisplayString:(V2TIMMessage *)msg {
if (msg.customElem == nil || msg.customElem.data == nil) {
return nil;
}
NSDictionary *param = [TUITool jsonData2Dictionary:msg.customElem.data];
if (param == nil || ![param isKindOfClass:[NSDictionary class]]) {
return nil;
}
NSString *businessID = param[@"businessID"];
if (![businessID isKindOfClass:[NSString class]]) {
return nil;
}
if (![businessID isEqualToString:BussinessID_GroupCreate] && ![param.allKeys containsObject:BussinessID_GroupCreate]) {
return nil;
}
NSString *localizableContent = param[@"content"];
NSNumber *cmd = param[@"cmd"];
if (cmd && [cmd isKindOfClass:NSNumber.class]) {
NSInteger command = [cmd integerValue];
if (command == 1) {
localizableContent = TIMCommonLocalizableString(TUICommunityCreateTipsMessage);
} else {
localizableContent = TIMCommonLocalizableString(TUIGroupCreateTipsMessage);
}
}
NSString * opUser = [self.class getOpUserName:msg]?:param[@"opUser"];
NSString *str = [NSString stringWithFormat:@"\"%@\" %@", opUser, localizableContent];
return rtlString(str);
}
@end

View File

@@ -0,0 +1,58 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
/**
* This document declares modules for implementing "more" units.
* - "More units", that is, several units that appear after clicking the "+" in the lower right corner of the chat interface.
* - At present, "More Units" provides four multimedia sending functions of shooting, video, picture and file, and you can also customize it.
* - TUIInputMoreCellData is responsible for storing the information needed for a series of "more" cells.
*/
#import <Foundation/Foundation.h>
@import UIKit;
NS_ASSUME_NONNULL_BEGIN
typedef void (^TUIInputMoreCallback)(NSDictionary *param);
/////////////////////////////////////////////////////////////////////////////////
//
// TUIInputMoreCellData
//
/////////////////////////////////////////////////////////////////////////////////
/**
* 【Module name】TUIInputMoreCellData
* 【Function description】"More units" data source
* - "More Units" is responsible for displaying in "More Views", showing the user the functionality contained in "More Views". At the same time, it serves as
* the entrance of each function and responds to user interaction events.
* - The data source is responsible for storing the information needed for a series of "more units".
*/
@interface TUIInputMoreCellData : NSObject
/**
* Image for single unit
* The icons of each unit are different, which are used to visually represent the function corresponding to the unit
*/
@property(nonatomic, strong) UIImage *image;
/**
* Name for single unit
* The names of each unit are different (such as Photo, Video, File, Album, etc.), which are used to display the corresponding functions of the unit in text
* form below the icon.
*/
@property(nonatomic, strong) NSString *title;
/**
* Callback for clicked
*/
@property(nonatomic, copy) TUIInputMoreCallback onClicked;
/**
* Prioriy for displaying in more menu list
* The larger the value, the higher the front in list
*/
@property(nonatomic, assign) NSInteger priority;
@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 "TUIInputMoreCellData.h"
@implementation TUIInputMoreCellData
@end

View File

@@ -0,0 +1,39 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
/******************************************************************************
*
* This file declares the TUIMemberCellData class.
* It provides a data source for the TUIMemberCell class, which is mainly used in the message read member list interface, etc.
*
******************************************************************************/
#import <TIMCommon/TIMCommonModel.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIMemberDescribeCellData : TUICommonCellData
@property(nonatomic, copy) NSString *title;
@property(nonatomic, copy) UIImage *icon;
@end
@interface TUIMemberCellData : TUICommonCellData
@property(nonatomic, copy) NSString *title; // member's display name
@property(nonatomic, copy) NSURL *avatarUrL; // member's avatar image url
@property(nonatomic, copy) NSString *detail; // optional, used to display more info
@property(nonatomic, copy) NSString *userID;
- (instancetype)initWithUserID:(nonnull NSString *)userID
nickName:(nullable NSString *)nickName
friendRemark:(nullable NSString *)friendRemark
nameCard:(nullable NSString *)nameCard
avatarUrl:(nonnull NSString *)avatarUrl
detail:(nullable NSString *)detail;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,47 @@
//
// TUIMemberCellData.m
// TUIChat
//
// Created by xia on 2022/3/14.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIMemberCellData.h"
@implementation TUIMemberDescribeCellData
@end
@implementation TUIMemberCellData
- (instancetype)initWithUserID:(nonnull NSString *)userID
nickName:(nullable NSString *)nickName
friendRemark:(nullable NSString *)friendRemark
nameCard:(nullable NSString *)nameCard
avatarUrl:(nonnull NSString *)avatarUrl
detail:(nullable NSString *)detail {
self = [super init];
if (userID.length > 0) {
_userID = userID;
}
if (avatarUrl.length > 0) {
_avatarUrL = [NSURL URLWithString:avatarUrl];
}
_detail = detail;
if (nameCard.length > 0) {
_title = nameCard;
} else if (friendRemark.length > 0) {
_title = friendRemark;
} else if (nickName.length > 0) {
_title = nickName;
} else {
_title = userID;
}
return self;
}
@end

View File

@@ -0,0 +1,67 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
/**
* This file declares the TUITextMessageCellData class.
* This class inherits from TUIBubbleMessageCellData and is used to store a series of data and information required by the text message unit.
*/
#import <TIMCommon/TUIBubbleMessageCellData.h>
#import <TIMCommon/TUIMessageCellData.h>
NS_ASSUME_NONNULL_BEGIN
/**
* 【Module name】 TUITextMessageCellData
* 【Function description】The datasource of text message unit.
* - Text message unit, which is the most common message unit in most message sending and receiving situations.
* - The text message unit data source provides a series of required data and information for the text message unit.
*/
@interface TUITextMessageCellData : TUIBubbleMessageCellData
/**
* Content of text message
*/
@property(nonatomic, strong) NSString *content;
@property(nonatomic, assign) BOOL isAudioCall;
@property(nonatomic, assign) BOOL isVideoCall;
@property(nonatomic, assign) BOOL isCaller;
@property(nonatomic, assign) BOOL showUnreadPoint;
/**
*
* Mutable strings.
* After the text message receives the content string, it is necessary to convert the string expression (such as [smile]) that may exist in the string into a
* picture expression. This string is responsible for storing the converted result of the above process.
*
*/
- (NSAttributedString *)getContentAttributedString:(UIFont *)textFont;
/**
*
* Get the display size of content string
*/
- (CGSize)getContentAttributedStringSize:(NSAttributedString *)attributeString maxTextSize:(CGSize)maxTextSize;
/**
* NSValue (NSRange) stores the converted string of emoji at the position of attributedString.
* NSAttributedString stores the string before emoji conversion, such as "[呲牙]".
* When the text is selected and copied, it is necessary to find the original string of emoji.
*/
@property(nonatomic, strong) NSMutableArray<NSDictionary<NSValue *, NSAttributedString *> *> *emojiLocations;
/**
* The size of the label which displays the text message content.
* Position the text message with the @textOrigin.
*/
@property(nonatomic, assign) CGSize textSize;
/**
* The origin of label which displays the text message content.
*/
@property(nonatomic, assign) CGPoint textOrigin;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,124 @@
//
// TUITextMessageCellData.m
// TXIMSDK_TUIKit_iOS
//
// Created by annidyfeng on 2019/5/21.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUITextMessageCellData.h"
#import <TIMCommon/NSString+TUIEmoji.h>
#import <TIMCommon/TIMCommonModel.h>
#import <TIMCommon/TIMDefine.h>
#import <TUICore/TUIThemeManager.h>
#ifndef CGFLOAT_CEIL
#ifdef CGFLOAT_IS_DOUBLE
#define CGFLOAT_CEIL(value) ceil(value)
#else
#define CGFLOAT_CEIL(value) ceilf(value)
#endif
#endif
@interface TUITextMessageCellData ()
@property(nonatomic, assign) CGSize size;
@property(nonatomic, assign) CGFloat containerWidth;
@property(nonatomic, strong) NSMutableAttributedString *attributedString;
@end
@implementation TUITextMessageCellData
{
NSString *_content;
}
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
TUITextMessageCellData *textData = [[TUITextMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
textData.content = message.textElem.text;
textData.reuseId = TTextMessageCell_ReuseId;
textData.status = Msg_Status_Init;
return textData;
}
+ (NSString *)getDisplayString:(V2TIMMessage *)message {
NSString *content = message.textElem.text;
return content.getLocalizableStringWithFaceContent;
}
- (Class)getReplyQuoteViewDataClass {
return NSClassFromString(@"TUITextReplyQuoteViewData");
}
- (Class)getReplyQuoteViewClass {
return NSClassFromString(@"TUITextReplyQuoteView");
}
- (instancetype)initWithDirection:(TMsgDirection)direction {
self = [super initWithDirection:direction];
if (self) {
if (direction == MsgDirectionIncoming) {
self.cellLayout = [TUIMessageCellLayout incommingTextMessageLayout];
} else {
self.cellLayout = [TUIMessageCellLayout outgoingTextMessageLayout];
}
}
return self;
}
- (void)setContent:(NSString *)content {
if (![_content isEqualToString:content]) {
_content = content;
_attributedString = nil;
}
}
- (NSString *)content {
return _content;
}
- (NSAttributedString *)getContentAttributedString:(UIFont *)textFont {
if (!_attributedString) {
_emojiLocations = [NSMutableArray array];
_attributedString = [self.content getFormatEmojiStringWithFont:textFont emojiLocations:_emojiLocations];
if (self.isAudioCall || self.isVideoCall) {
NSTextAttachment *attchment = [[NSTextAttachment alloc] init];
UIImage *image = nil;
if (self.isAudioCall) {
image = TUIChatCommonBundleImage(@"audio_call");
}
if (self.isVideoCall) {
if (self.isCaller) {
image = TUIChatCommonBundleImage(@"video_call_self");
} else {
image = TUIChatCommonBundleImage(@"video_call");
}
}
attchment.image = image;
attchment.bounds = CGRectMake(0, -(textFont.lineHeight - textFont.pointSize) / 2, 16, 16);
NSAttributedString *imageString = [NSAttributedString attributedStringWithAttachment:(NSTextAttachment *)(attchment)];
NSAttributedString *spaceString = [[NSAttributedString alloc] initWithString:@" " attributes:@{NSFontAttributeName : textFont}];
if (self.isCaller) {
[_attributedString appendAttributedString:spaceString];
[_attributedString appendAttributedString:imageString];
} else {
[_attributedString insertAttributedString:spaceString atIndex:0];
[_attributedString insertAttributedString:imageString atIndex:0];
}
}
}
return _attributedString;
}
- (CGSize)getContentAttributedStringSize:(NSAttributedString *)attributeString maxTextSize:(CGSize)maxTextSize {
CGRect rect = [attributeString boundingRectWithSize:maxTextSize
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
context:nil];
CGFloat width = CGFLOAT_CEIL(rect.size.width);
CGFloat height = CGFLOAT_CEIL(rect.size.height);
return CGSizeMake(width, height);
}
@end

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

View File

@@ -0,0 +1,21 @@
//
// TUIEvaluationCellData.h
// TUIChat
//
// Created by xia on 2022/6/10.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <TIMCommon/TUIBubbleMessageCellData.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIEvaluationCellData : TUIBubbleMessageCellData
@property(nonatomic, assign) NSInteger score;
@property(nonatomic, copy) NSString *desc;
@property(nonatomic, copy) NSString *comment;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,40 @@
//
// TUIEvaluationCellData.m
// TUIChat
//
// Created by xia on 2022/6/10.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIEvaluationCellData.h"
@implementation TUIEvaluationCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data options:NSJSONReadingAllowFragments error:nil];
if (param == nil) {
return nil;
}
TUIEvaluationCellData *cellData = [[TUIEvaluationCellData alloc] initWithDirection:message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming];
cellData.innerMessage = message;
cellData.desc = message.customElem.desc;
cellData.score = [param[@"score"] integerValue];
cellData.comment = param[@"comment"];
return cellData;
}
+ (NSString *)getDisplayString:(V2TIMMessage *)message {
return message.customElem.desc;
}
- (CGSize)contentSize {
CGRect rect = [self.comment boundingRectWithSize:CGSizeMake(215, MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:15]}
context:nil];
CGSize size = CGSizeMake(245, ceilf(rect.size.height));
size.height += self.comment.length > 0 ? 88 : 50;
return size;
}
@end

View File

@@ -0,0 +1,19 @@
//
// MyCustomCellData.h
// TUIKitDemo
//
// Created by annidyfeng on 2019/6/10.
// Copyright © 2019 Tencent. All rights reserved.
//
#import <TIMCommon/TUIBubbleMessageCellData.h>
#import <TIMCommon/TUIMessageCellData.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUILinkCellData : TUIBubbleMessageCellData
@property NSString *text;
@property NSString *link;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,38 @@
//
// MyCustomCellData.m
// TUIKitDemo
//
// Created by annidyfeng on 2019/6/10.
// Copyright © 2019 Tencent. All rights reserved.
//
#import "TUILinkCellData.h"
@implementation TUILinkCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data options:NSJSONReadingAllowFragments error:nil];
TUILinkCellData *cellData = [[TUILinkCellData alloc] initWithDirection:message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming];
cellData.msgID = message.msgID;
cellData.text = param[@"text"];
cellData.link = param[@"link"];
cellData.avatarUrl = [NSURL URLWithString:message.faceURL];
return cellData;
}
+ (NSString *)getDisplayString:(V2TIMMessage *)message {
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data options:NSJSONReadingAllowFragments error:nil];
return param[@"text"];
}
- (CGSize)contentSize {
CGFloat textMaxWidth = 245.f;
CGRect rect = [self.text boundingRectWithSize:CGSizeMake(textMaxWidth, MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:15]}
context:nil];
CGSize size = CGSizeMake(textMaxWidth + 15, rect.size.height + 56);
return size;
}
@end

View File

@@ -0,0 +1,17 @@
//
// TUILocalTipsCellData.h
// TUIChat
//
// Created by yiliangwang on 2025/3/18.
// Copyright © 2025 Tencent. All rights reserved.
//
#import <TIMCommon/TUISystemMessageCellData.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUILocalTipsCellData : TUISystemMessageCellData
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,29 @@
//
// TUILocalTipsCellData.m
// TUIChat
//
// Created by yiliangwang on 2025/3/18.
// Copyright © 2025 Tencent. All rights reserved.
//
#import "TUILocalTipsCellData.h"
@implementation TUILocalTipsCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data options:NSJSONReadingAllowFragments error:nil];
TUILocalTipsCellData *cellData = [[TUILocalTipsCellData alloc] initWithDirection:MsgDirectionIncoming];
cellData.innerMessage = message;
cellData.msgID = message.msgID;
cellData.content = param[@"content"];
cellData.reuseId = TSystemMessageCell_ReuseId;
return cellData;
}
+ (NSString *)getDisplayString:(V2TIMMessage *)message {
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data options:NSJSONReadingAllowFragments error:nil];
return param[@"content"];
}
@end

View File

@@ -0,0 +1,23 @@
//
// TUIOrderCellData.h
// TUIChat
//
// Created by xia on 2022/6/10.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <TIMCommon/TUIBubbleMessageCellData.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIOrderCellData : TUIBubbleMessageCellData
@property(nonatomic, copy) NSString *title;
@property(nonatomic, copy) NSString *desc;
@property(nonatomic, copy) NSString *price;
@property(nonatomic, copy) NSString *imageUrl;
@property(nonatomic, copy) NSString *link;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,37 @@
//
// TUIOrderCellData.m
// TUIChat
//
// Created by xia on 2022/6/10.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIOrderCellData.h"
@implementation TUIOrderCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data options:NSJSONReadingAllowFragments error:nil];
TUIOrderCellData *cellData = [[TUIOrderCellData alloc] initWithDirection:message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming];
cellData.innerMessage = message;
cellData.msgID = message.msgID;
cellData.title = param[@"title"];
cellData.desc = param[@"description"];
cellData.imageUrl = param[@"imageUrl"];
cellData.link = param[@"link"];
cellData.price = param[@"price"];
cellData.avatarUrl = [NSURL URLWithString:message.faceURL];
return cellData;
}
+ (NSString *)getDisplayString:(V2TIMMessage *)message {
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data options:NSJSONReadingAllowFragments error:nil];
return param[@"title"];
}
- (CGSize)contentSize {
CGSize size = CGSizeMake(245, 80);
return size;
}
@end

View File

@@ -0,0 +1,17 @@
//
// TUIFileReplyQuoteViewData.h
// TUIChat
//
// Created by harvy on 2021/11/25.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIVoiceReplyQuoteViewData.h"
NS_ASSUME_NONNULL_BEGIN
@interface TUIFileReplyQuoteViewData : TUIVoiceReplyQuoteViewData
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,31 @@
//
// TUIFileReplyQuoteViewData.m
// TUIChat
//
// Created by harvy on 2021/11/25.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIFileReplyQuoteViewData.h"
#import <TUICore/TUIThemeManager.h>
#import "TUIFileMessageCellData.h"
@implementation TUIFileReplyQuoteViewData
+ (instancetype)getReplyQuoteViewData:(TUIMessageCellData *)originCellData {
if (originCellData == nil) {
return nil;
}
if (![originCellData isKindOfClass:TUIFileMessageCellData.class]) {
return nil;
}
TUIFileReplyQuoteViewData *myData = [[TUIFileReplyQuoteViewData alloc] init];
myData.text = [(TUIFileMessageCellData *)originCellData fileName];
myData.icon = TUIChatCommonBundleImage(@"msg_file");
myData.originCellData = originCellData;
return myData;
}
@end

View File

@@ -0,0 +1,32 @@
//
// TUIImageReplyQuoteViewData.h
// TUIChat
//
// Created by harvy on 2021/11/25.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIReplyMessageCellData.h"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, TUIImageReplyQuoteStatus) {
TUIImageReplyQuoteStatusInit,
TUIImageReplyQuoteStatusDownloading,
TUIImageReplyQuoteStatusSuccess,
TUIImageReplyQuoteStatusFailed,
};
@interface TUIImageReplyQuoteViewData : TUIReplyQuoteViewData
@property(nonatomic, assign) TUIImageReplyQuoteStatus imageStatus;
@property(nonatomic, strong) UIImage *image;
@property(nonatomic, assign) CGSize imageSize;
+ (CGSize)displaySizeWithOriginSize:(CGSize)originSize;
- (void)downloadImage;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,75 @@
//
// TUIImageReplyQuoteViewData.m
// TUIChat
//
// Created by harvy on 2021/11/25.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIImageReplyQuoteViewData.h"
#import "TUIImageMessageCellData.h"
@implementation TUIImageReplyQuoteViewData
+ (instancetype)getReplyQuoteViewData:(TUIMessageCellData *)originCellData {
if (originCellData == nil) {
return nil;
}
if (![originCellData isKindOfClass:TUIImageMessageCellData.class]) {
return nil;
}
TUIImageReplyQuoteViewData *myData = [[TUIImageReplyQuoteViewData alloc] init];
V2TIMImage *thumb = nil;
for (V2TIMImage *image in originCellData.innerMessage.imageElem.imageList) {
if (image.type == V2TIM_IMAGE_TYPE_THUMB) {
thumb = image;
break;
}
}
myData.imageSize = [TUIImageReplyQuoteViewData displaySizeWithOriginSize:CGSizeMake(thumb ? thumb.width : 60, thumb ? thumb.height : 60)];
myData.originCellData = originCellData;
myData.imageStatus = TUIImageReplyQuoteStatusInit;
return myData;
}
- (CGSize)contentSize:(CGFloat)maxWidth {
return self.imageSize;
}
+ (CGSize)displaySizeWithOriginSize:(CGSize)originSize {
if (originSize.width == 0 || originSize.width == 0) {
return CGSizeZero;
}
CGFloat max = 60;
CGFloat w = 0, h = 0;
if (originSize.width > originSize.height) {
w = max;
h = max * originSize.height / originSize.width;
} else {
w = max * originSize.width / originSize.height;
h = max;
}
return CGSizeMake(w, h);
}
- (void)downloadImage {
@weakify(self);
self.imageStatus = TUIImageReplyQuoteStatusDownloading;
if ([self.originCellData isKindOfClass:TUIImageMessageCellData.class]) {
TUIImageMessageCellData *imageData = (TUIImageMessageCellData *)self.originCellData;
[imageData downloadImage:TImage_Type_Thumb
finish:^{
@strongify(self);
self.image = imageData.thumbImage;
self.imageStatus = TUIImageReplyQuoteStatusSuccess;
if (self.onFinish) {
self.onFinish();
}
}];
}
}
@end

View File

@@ -0,0 +1,20 @@
//
// TUIMergeReplyQuoteViewData.h
// TUIChat
//
// Created by harvy on 2021/11/25.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIReplyMessageCellData.h"
NS_ASSUME_NONNULL_BEGIN
@interface TUIMergeReplyQuoteViewData : TUIReplyQuoteViewData
@property(nonatomic, copy) NSString *title;
@property(nonatomic, copy) NSString *abstract;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,43 @@
//
// TUIMergeReplyQuoteViewData.m
// TUIChat
//
// Created by harvy on 2021/11/25.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIMergeReplyQuoteViewData.h"
#import <TIMCommon/NSString+TUIEmoji.h>
#import "TUIMergeMessageCellData.h"
@implementation TUIMergeReplyQuoteViewData
+ (instancetype)getReplyQuoteViewData:(TUIMessageCellData *)originCellData {
if (originCellData == nil) {
return nil;
}
if (![originCellData isKindOfClass:TUIMergeMessageCellData.class]) {
return nil;
}
TUIMergeReplyQuoteViewData *myData = [[TUIMergeReplyQuoteViewData alloc] init];
myData.title = [(TUIMergeMessageCellData *)originCellData title];
NSAttributedString *abstract = [(TUIMergeMessageCellData *)originCellData abstractAttributedString];
myData.abstract = abstract.string;
myData.originCellData = originCellData;
return myData;
}
- (CGSize)contentSize:(CGFloat)maxWidth {
CGFloat singleHeight = [UIFont systemFontOfSize:10.0].lineHeight;
NSAttributedString *titleAttributeString = [self.title getFormatEmojiStringWithFont:[UIFont systemFontOfSize:10.0] emojiLocations:nil];
CGRect titleRect = [titleAttributeString boundingRectWithSize:CGSizeMake(maxWidth, singleHeight)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
context:nil];
CGFloat width = titleRect.size.width;
CGFloat height = titleRect.size.height;
return CGSizeMake(MIN(width, maxWidth), height);
}
@end

View File

@@ -0,0 +1,107 @@
//
// TUIReplyMessageCellData.h
// TUIChat
//
// Created by harvy on 2021/11/11.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <ImSDK_Plus/ImSDK_Plus.h>
#import <TIMCommon/TUIBubbleMessageCellData.h>
#import "TUIChatDefine.h"
#import "TUIReplyQuoteViewData.h"
NS_ASSUME_NONNULL_BEGIN
@class TUIReplyMessageCellData;
@interface TUIReplyMessageCellData : TUIBubbleMessageCellData
/**
* The original message ID
*/
@property(nonatomic, copy) NSString *__nullable originMsgID;
/**
* The default abstract of original message
*/
@property(nonatomic, copy) NSString *__nullable msgAbstract;
/**
* The sender of original message
*/
@property(nonatomic, copy) NSString *__nullable sender;
/**
* The sender of original message
*/
@property(nonatomic, copy) NSString *__nullable faceURL;
/**
* The type of original message
*/
@property(nonatomic, assign) V2TIMElemType originMsgType;
/**
*
* Original message
*/
@property(nonatomic, strong) V2TIMMessage *__nullable originMessage;
@property(nonatomic, strong) TUIMessageCellData *originCellData;
@property(nonatomic, strong) TUIReplyQuoteViewData *quoteData;
@property(nonatomic, assign) BOOL showRevokedOriginMessage;
/**
* The content of replying the original message
*/
@property(nonatomic, copy) NSString *content;
@property(nonatomic, strong, readonly) NSAttributedString *attributeString;
/**
* The size of quote view, including @senderSize and @quotePlaceholderSize
*/
@property(nonatomic, assign) CGSize quoteSize;
/**
* The size of label which displays the sender displayname
*/
@property(nonatomic, assign) CGSize senderSize;
/**
* The size of customize quote view
*/
@property(nonatomic, assign) CGSize quotePlaceholderSize;
/**
* The size of label which displays the content of replying the original message.
*/
@property(nonatomic, assign) CGSize replyContentSize;
@property(nonatomic, copy) TUIReplyAsyncLoadFinish onFinish;
/**
* The message ID of the root message which is replyed at first.
*/
@property(nonatomic, copy) NSString *messageRootID;
@property(nonatomic) UIColor *textColor;
@property(nonatomic, strong) NSString *selectContent;
@property(nonatomic, strong) NSMutableArray<NSDictionary<NSValue *, NSAttributedString *> *> *emojiLocations;
// Deprecated
// Search `loadOriginMessageFromReplyData` in TUIMessageDataProvider+MessageDeal
//- (void)loadOriginMessage:(void(^)(void))callback;
- (TUIReplyQuoteViewData *)getQuoteData:(TUIMessageCellData *)originCellData;
- (CGSize)quotePlaceholderSizeWithType:(V2TIMElemType)type data:(TUIReplyQuoteViewData *)data;
@end
@interface TUIReferenceMessageCellData : TUIReplyMessageCellData
@property(nonatomic, assign) CGSize textSize;
@property(nonatomic, assign) CGPoint textOrigin;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,173 @@
//
// TUIReplyMessageCellData.m
// TUIChat
//
// Created by harvy on 2021/11/11.
// Copyright © 2023 Tencent. All rights reserved.
//
/**
The protocol format of the custom field cloudMessageData of the message
{
"messageReply":{
"messageID": "xxxx0xxx=xx",
"messageAbstract":"origin message abstract..."
"messageSender":"NickName/99618",
"messageType": "1/2/..",
"version":"1",
}
}
*/
#import "TUIReplyMessageCellData.h"
#import <TIMCommon/NSString+TUIEmoji.h>
#import "TUIFileMessageCellData.h"
#import "TUIImageMessageCellData.h"
#import "TUIMergeMessageCellData.h"
#import "TUITextMessageCellData.h"
#import "TUIVideoMessageCellData.h"
#import "TUIVoiceMessageCellData.h"
#import "TUICloudCustomDataTypeCenter.h"
#import "TUIFileReplyQuoteViewData.h"
#import "TUIImageReplyQuoteViewData.h"
#import "TUIMergeReplyQuoteViewData.h"
#import "TUIReplyPreviewData.h"
#import "TUITextReplyQuoteViewData.h"
#import "TUIVideoReplyQuoteViewData.h"
#import "TUIVoiceReplyQuoteViewData.h"
@implementation TUIReplyMessageCellData
{
NSString *_sender;
}
- (void)setSender:(NSString *)sender {
_sender = sender;
}
- (NSString *__nullable)sender {
if (self.originMessage) {
return self.originMessage.nameCard ? : (self.originMessage.friendRemark ? : (self.originMessage.nickName ? : self.originMessage.sender));
}
return _sender;
}
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
if (message.cloudCustomData == nil) {
return nil;
}
__block TUIReplyMessageCellData *replyData = nil;
[message doThingsInContainsCloudCustomOfDataType:TUICloudCustomDataType_MessageReply
callback:^(BOOL isContains, id obj) {
if (isContains) {
if (obj && [obj isKindOfClass:NSDictionary.class]) {
NSDictionary *reply = (NSDictionary *)obj;
// This message is a "reply message"
replyData = [[TUIReplyMessageCellData alloc]
initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
replyData.reuseId = TReplyMessageCell_ReuseId;
replyData.originMsgID = reply[@"messageID"];
replyData.msgAbstract = reply[@"messageAbstract"];
replyData.sender = reply[@"messageSender"];
replyData.originMsgType = (V2TIMElemType)[reply[@"messageType"] integerValue];
replyData.content = message.textElem.text;
replyData.messageRootID = reply[@"messageRootID"];
}
}
}];
return replyData;
}
- (instancetype)initWithDirection:(TMsgDirection)direction {
self = [super initWithDirection:direction];
if (self) {
if (direction == MsgDirectionIncoming) {
self.cellLayout = [TUIMessageCellLayout incommingTextMessageLayout];
} else {
self.cellLayout = [TUIMessageCellLayout outgoingTextMessageLayout];
}
_emojiLocations = [NSMutableArray array];
}
return self;
}
- (CGSize)quotePlaceholderSizeWithType:(V2TIMElemType)type data:(TUIReplyQuoteViewData *)data {
if (data == nil) {
return CGSizeMake(20, 20);
}
return [data contentSize:TReplyQuoteView_Max_Width - 12];
}
- (TUIReplyQuoteViewData *)getQuoteData:(TUIMessageCellData *)originCellData {
TUIReplyQuoteViewData *quoteData = nil;
Class class = [originCellData getReplyQuoteViewDataClass];
BOOL hasRiskContent = originCellData.innerMessage.hasRiskContent;
if (hasRiskContent && [TIMConfig isClassicEntrance]){
// Return text reply data in default
TUITextReplyQuoteViewData *myData = [[TUITextReplyQuoteViewData alloc] init];
myData.text = [TUIReplyPreviewData displayAbstract:self.originMsgType abstract:self.msgAbstract withFileName:NO isRisk:hasRiskContent];
quoteData = myData;
}
else if (class && [class respondsToSelector:@selector(getReplyQuoteViewData:)]) {
quoteData = [class getReplyQuoteViewData:originCellData];
}
else {
}
if (quoteData == nil) {
//
// Return text reply data in default
TUITextReplyQuoteViewData *myData = [[TUITextReplyQuoteViewData alloc] init];
myData.text = [TUIReplyPreviewData displayAbstract:self.originMsgType abstract:self.msgAbstract withFileName:NO isRisk:hasRiskContent];
quoteData = myData;
}
quoteData.originCellData = originCellData;
@weakify(self);
quoteData.onFinish = ^{
@strongify(self);
if (self.onFinish) {
self.onFinish();
}
};
return quoteData;
}
@end
@implementation TUIReferenceMessageCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
if (message.cloudCustomData == nil) {
return nil;
}
__block TUIReplyMessageCellData *replyData = nil;
[message doThingsInContainsCloudCustomOfDataType:TUICloudCustomDataType_MessageReference
callback:^(BOOL isContains, id obj) {
if (isContains) {
if (obj && [obj isKindOfClass:NSDictionary.class]) {
NSDictionary *reply = (NSDictionary *)obj;
if ([reply isKindOfClass:NSDictionary.class]) {
// This message is quote messagewhich indicating the original message
replyData = [[TUIReferenceMessageCellData alloc]
initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
replyData.reuseId = TUIReferenceMessageCell_ReuseId;
replyData.originMsgID = reply[@"messageID"];
replyData.msgAbstract = reply[@"messageAbstract"];
replyData.sender = reply[@"messageSender"];
replyData.originMsgType = (V2TIMElemType)[reply[@"messageType"] integerValue];
replyData.content = message.textElem.text; // text only
}
}
}
}];
return replyData;
}
@end

View File

@@ -0,0 +1,55 @@
//
// TUIReplyPreviewData.h
// TUIChat
//
// Created by wyl on 2022/3/22.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "TUIChatDefine.h"
@class V2TIMMessage;
@interface TUIReplyPreviewData : NSObject
/**
* The message ID of the replyed original message
*/
@property(nonatomic, copy) NSString *msgID;
/**
* The abstract of the replyed original message
*/
@property(nonatomic, copy) NSString *msgAbstract;
/**
* The sender's displayname of the replyed original message. Nickname is prior than userID.
*/
@property(nonatomic, copy) NSString *sender;
/**
* The faceURL of the replyed original message
*/
@property(nonatomic, copy) NSString *faceURL;
/**
* The message type of the replyed original message. For details, see the enumeration value of V2TIMElemType.
*/
@property(nonatomic, assign) NSInteger type;
/**
* The replyed original message
*/
@property(nonatomic, strong) V2TIMMessage *originMessage;
// Message reply root RootID (not necessarily the msgID of the originMessage above, but the ID of the message at the top)
@property(nonatomic, copy) NSString *messageRootID;
+ (NSString *)displayAbstract:(NSInteger)type abstract:(NSString *)abstract withFileName:(BOOL)withFilename isRisk:(BOOL)isRisk;
@end
@interface TUIReferencePreviewData : TUIReplyPreviewData
@end

View File

@@ -0,0 +1,39 @@
//
// TUIReplyPreviewData.m
// TUIChat
//
// Created by wyl on 2022/3/22.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIReplyPreviewData.h"
#import <TIMCommon/TIMDefine.h>
@implementation TUIReplyPreviewData
+ (NSString *)displayAbstract:(NSInteger)type abstract:(NSString *)abstract withFileName:(BOOL)withFilename isRisk:(BOOL)isRisk {
NSString *text = abstract;
if (type == V2TIM_ELEM_TYPE_IMAGE) {
text = isRisk? TIMCommonLocalizableString(TUIkitMessageTypeRiskImage):TIMCommonLocalizableString(TUIkitMessageTypeImage);
} else if (type == V2TIM_ELEM_TYPE_VIDEO) {
text = isRisk? TIMCommonLocalizableString(TUIkitMessageTypeRiskVideo):TIMCommonLocalizableString(TUIkitMessageTypeVideo);
} else if (type == V2TIM_ELEM_TYPE_SOUND) {
text = isRisk? TIMCommonLocalizableString(TUIkitMessageTypeRiskVoice):TIMCommonLocalizableString(TUIKitMessageTypeVoice);
} else if (type == V2TIM_ELEM_TYPE_FACE) {
text = TIMCommonLocalizableString(TUIKitMessageTypeAnimateEmoji);
} else if (type == V2TIM_ELEM_TYPE_FILE) {
if (withFilename) {
text = [NSString stringWithFormat:@"%@%@", TIMCommonLocalizableString(TUIkitMessageTypeFile), abstract];
;
} else {
text = TIMCommonLocalizableString(TUIkitMessageTypeFile);
}
}
return text;
}
@end
@implementation TUIReferencePreviewData
@end

View File

@@ -0,0 +1,37 @@
//
// TUIReplyQuoteViewData.h
// TUIChat
//
// Created by harvy on 2021/11/25.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "TUIChatDefine.h"
@class TUIMessageCellData;
NS_ASSUME_NONNULL_BEGIN
@interface TUIReplyQuoteViewData : NSObject
+ (instancetype)getReplyQuoteViewData:(TUIMessageCellData *)originCellData;
- (CGSize)contentSize:(CGFloat)maxWidth;
/**
* If you want to download the custom reply content asynchronously, you need to call the callback after the download is complete, and the TUI will be
* automatically refreshed.
*/
@property(nonatomic, copy) TUIReplyQuoteAsyncLoadFinish onFinish;
@property(nonatomic, strong) TUIMessageCellData *originCellData;
@property(nonatomic, assign) BOOL supportForReply;
@property(nonatomic, assign) BOOL showRevokedOriginMessage;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,21 @@
//
// TUIReplyQuoteViewData.m
// TUIChat
//
// Created by harvy on 2021/11/25.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIReplyQuoteViewData.h"
@implementation TUIReplyQuoteViewData
+ (instancetype)getReplyQuoteViewData:(TUIMessageCellData *)originCellData {
return nil;
}
- (CGSize)contentSize:(CGFloat)maxWidth {
return CGSizeZero;
}
@end

View File

@@ -0,0 +1,19 @@
//
// TUITextReplyQuoteViewData.h
// TUIChat
//
// Created by harvy on 2021/11/25.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIReplyMessageCellData.h"
NS_ASSUME_NONNULL_BEGIN
@interface TUITextReplyQuoteViewData : TUIReplyQuoteViewData
@property(nonatomic, copy) NSString *text;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,54 @@
//
// TUITextReplyQuoteViewData.m
// TUIChat
//
// Created by harvy on 2021/11/25.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUITextReplyQuoteViewData.h"
#import <TIMCommon/NSString+TUIEmoji.h>
#import "TUITextMessageCellData.h"
@implementation TUITextReplyQuoteViewData
+ (instancetype)getReplyQuoteViewData:(TUIMessageCellData *)originCellData {
if (originCellData == nil) {
return nil;
}
if (![originCellData isKindOfClass:TUITextMessageCellData.class]) {
return nil;
}
TUITextReplyQuoteViewData *myData = [[TUITextReplyQuoteViewData alloc] init];
myData.text = [(TUITextMessageCellData *)originCellData content];
myData.originCellData = originCellData;
return myData;
}
- (CGSize)contentSize:(CGFloat)maxWidth {
NSAttributedString *attributeString = nil;
BOOL showRevokeStr = (self.originCellData.innerMessage.status == V2TIM_MSG_STATUS_LOCAL_REVOKED) &&
!self.showRevokedOriginMessage;
if (showRevokeStr) {
NSString * revokeStr = self.supportForReply?
TIMCommonLocalizableString(TUIKitRepliesOriginMessageRevoke):
TIMCommonLocalizableString(TUIKitReferenceOriginMessageRevoke);
attributeString = [revokeStr getFormatEmojiStringWithFont:[UIFont systemFontOfSize:10.0] emojiLocations:nil];
} else {
attributeString = [self.text getFormatEmojiStringWithFont:[UIFont systemFontOfSize:10.0] emojiLocations:nil];
}
CGSize size = [@"0" sizeWithAttributes:@{NSFontAttributeName : [UIFont systemFontOfSize:10.0]}];
CGRect rect = [attributeString boundingRectWithSize:CGSizeMake(maxWidth, size.height * 2)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
context:nil];
CGFloat h = rect.size.height < size.height * 2 ? rect.size.height : size.height * 2;
if (showRevokeStr && self.supportForReply) {
h = size.height *2;
}
return CGSizeMake(rect.size.width, h);
}
@end

View File

@@ -0,0 +1,17 @@
//
// TUIVideoReplyQuoteViewData.h
// TUIChat
//
// Created by harvy on 2021/11/25.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIImageReplyQuoteViewData.h"
NS_ASSUME_NONNULL_BEGIN
@interface TUIVideoReplyQuoteViewData : TUIImageReplyQuoteViewData
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,47 @@
//
// TUIVideoReplyQuoteViewData.m
// TUIChat
//
// Created by harvy on 2021/11/25.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIVideoReplyQuoteViewData.h"
#import "TUIVideoMessageCellData.h"
@implementation TUIVideoReplyQuoteViewData
+ (instancetype)getReplyQuoteViewData:(TUIMessageCellData *)originCellData {
if (originCellData == nil) {
return nil;
}
if (![originCellData isKindOfClass:TUIVideoMessageCellData.class]) {
return nil;
}
TUIVideoReplyQuoteViewData *myData = [[TUIVideoReplyQuoteViewData alloc] init];
CGSize snapSize = CGSizeMake(originCellData.innerMessage.videoElem ? originCellData.innerMessage.videoElem.snapshotWidth : 0,
originCellData.innerMessage.videoElem ? originCellData.innerMessage.videoElem.snapshotHeight : 0);
myData.imageSize = [TUIVideoReplyQuoteViewData displaySizeWithOriginSize:snapSize];
myData.originCellData = originCellData;
return myData;
}
- (void)downloadImage {
[super downloadImage];
@weakify(self);
if ([self.originCellData isKindOfClass:TUIVideoMessageCellData.class]) {
TUIVideoMessageCellData *videoData = (TUIVideoMessageCellData *)self.originCellData;
[videoData downloadThumb:^{
@strongify(self);
self.image = videoData.thumbImage;
if (self.onFinish) {
self.onFinish();
}
}];
}
}
@end

View File

@@ -0,0 +1,19 @@
//
// TUIVoiceReplyQuoteViewData.h
// TUIChat
//
// Created by harvy on 2021/11/25.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUITextReplyQuoteViewData.h"
NS_ASSUME_NONNULL_BEGIN
@interface TUIVoiceReplyQuoteViewData : TUITextReplyQuoteViewData
@property(nonatomic, strong) UIImage *icon;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,41 @@
//
// TUIVoiceReplyQuoteViewData.m
// TUIChat
//
// Created by harvy on 2021/11/25.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIVoiceReplyQuoteViewData.h"
#import <TIMCommon/NSString+TUIEmoji.h>
#import <TUICore/TUIThemeManager.h>
#import "TUIVoiceMessageCellData.h"
@implementation TUIVoiceReplyQuoteViewData
+ (instancetype)getReplyQuoteViewData:(TUIMessageCellData *)originCellData {
if (originCellData == nil) {
return nil;
}
if (![originCellData isKindOfClass:TUIVoiceMessageCellData.class]) {
return nil;
}
TUIVoiceReplyQuoteViewData *myData = [[TUIVoiceReplyQuoteViewData alloc] init];
myData.text = [NSString stringWithFormat:@"%d\"", [(TUIVoiceMessageCellData *)originCellData duration]];
myData.icon = TUIChatCommonBundleImage(@"voice_reply");
myData.originCellData = originCellData;
return myData;
}
- (CGSize)contentSize:(CGFloat)maxWidth {
CGFloat marginWidth = 18;
CGSize size = [@"0" sizeWithAttributes:@{NSFontAttributeName : [UIFont systemFontOfSize:10.0]}];
CGRect rect = [self.text boundingRectWithSize:CGSizeMake(maxWidth - marginWidth, size.height)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:10.0]}
context:nil];
return CGSizeMake(rect.size.width + marginWidth, size.height);
}
@end