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,62 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
#import <Foundation/Foundation.h>
#import <TIMCommon/TUIMessageCellData.h>
#import "TUIChatConversationModel.h"
@class TUIChatBaseDataProvider;
NS_ASSUME_NONNULL_BEGIN
@protocol TUIChatBaseDataProviderDelegate <NSObject>
@required
- (NSString *)dataProvider:(TUIChatBaseDataProvider *)dataProvider mergeForwardTitleWithMyName:(NSString *)name;
- (NSString *)dataProvider:(TUIChatBaseDataProvider *)dataProvider mergeForwardMsgAbstactForMessage:(V2TIMMessage *)message;
- (void)dataProvider:(TUIChatBaseDataProvider *)dataProvider sendMessage:(V2TIMMessage *)message;
- (void)onSelectPhotoMoreCellData;
- (void)onTakePictureMoreCellData;
- (void)onTakeVideoMoreCellData;
- (void)onMultimediaRecordMoreCellData;
- (void)onSelectFileMoreCellData;
@end
@interface TUIChatBaseDataProvider : NSObject
@property(nonatomic, weak) id<TUIChatBaseDataProviderDelegate> delegate;
- (void)getForwardMessageWithCellDatas:(NSArray<TUIMessageCellData *> *)uiMsgs
toTargets:(NSArray<TUIChatConversationModel *> *)targets
Merge:(BOOL)merge
ResultBlock:(void (^)(TUIChatConversationModel *targetConversation, NSArray<V2TIMMessage *> *msgs))resultBlock
fail:(nullable V2TIMFail)fail;
- (NSString *)abstractDisplayWithMessage:(V2TIMMessage *)msg;
@end
#pragma mark - TUIChatBaseDataProvider (IMSDK)
@interface TUIChatBaseDataProvider (IMSDK)
+ (void)getTotalUnreadMessageCountWithSuccBlock:(void (^)(UInt64 totalCount))succ fail:(nullable V2TIMFail)fail;
+ (void)saveDraftWithConversationID:(NSString *)conversationId Text:(NSString *)text;
+ (void)findMessages:(NSArray *)msgIDs callback:(void (^)(BOOL succ, NSString *error_message, NSArray *msgs))callback;
#pragma mark - C2C
+ (void)getFriendInfoWithUserId:(nullable NSString *)userID
SuccBlock:(void (^)(V2TIMFriendInfoResult *friendInfoResult))succ
failBlock:(nullable V2TIMFail)fail;
+ (void)getUserInfoWithUserId:(NSString *)userID SuccBlock:(void (^)(V2TIMUserFullInfo *userInfo))succ failBlock:(nullable V2TIMFail)fail;
#pragma mark - Group
+ (void)getGroupInfoWithGroupID:(NSString *)groupID SuccBlock:(void (^)(V2TIMGroupInfoResult *groupResult))succ failBlock:(nullable V2TIMFail)fail;
+ (void)insertLocalTipsMessage:(NSString *)content chatID:(NSString *)chatID isGroup:(BOOL)isGroup;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,258 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
@import ImSDK_Plus;
#import <objc/runtime.h>
#import <TUICore/NSDictionary+TUISafe.h>
#import <TUICore/TUICore.h>
#import <TUICore/TUIThemeManager.h>
#import "TUIChatBaseDataProvider.h"
#import "TUIMessageBaseDataProvider.h"
#import <TUICore/TUILogin.h>
#import "TUIChatDefine.h"
#define Input_SendBtn_Key @"Input_SendBtn_Key"
#define Input_SendBtn_Title @"Input_SendBtn_Title"
#define Input_SendBtn_ImageName @"Input_SendBtn_ImageName"
static NSArray *gCustomInputBtnInfo = nil;
@implementation TUIChatBaseDataProvider
+ (void)initialize {
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(onChangeLanguage) name:TUIChangeLanguageNotification object:nil];
}
+ (void)onChangeLanguage {
gCustomInputBtnInfo = nil;
}
+ (NSArray *)customInputBtnInfo {
if (gCustomInputBtnInfo == nil) {
gCustomInputBtnInfo = @[ @{
Input_SendBtn_Key : TUIInputMoreCellKey_Link,
Input_SendBtn_Title : TIMCommonLocalizableString(TUIKitMoreLink),
Input_SendBtn_ImageName : @"chat_more_link_img"
} ];
}
return gCustomInputBtnInfo;
}
- (void)getForwardMessageWithCellDatas:(NSArray<TUIMessageCellData *> *)uiMsgs
toTargets:(NSArray<TUIChatConversationModel *> *)targets
Merge:(BOOL)merge
ResultBlock:(void (^)(TUIChatConversationModel *targetConversation, NSArray<V2TIMMessage *> *msgs))resultBlock
fail:(nullable V2TIMFail)fail {
if (uiMsgs.count == 0) {
if (fail) {
fail(ERR_SVR_PROFILE_INVALID_PARAMETERS, @"uiMsgs is empty");
}
return;
}
dispatch_apply(targets.count, dispatch_get_global_queue(0, 0), ^(size_t index) {
TUIChatConversationModel *convCellData = targets[index];
NSMutableArray *tmpMsgs = [NSMutableArray array];
for (TUIMessageCellData *uiMsg in uiMsgs) {
V2TIMMessage *msg = uiMsg.innerMessage;
if (msg) {
[tmpMsgs addObject:msg];
}
}
NSArray *msgs = [NSArray arrayWithArray:tmpMsgs];
msgs = [msgs sortedArrayUsingComparator:^NSComparisonResult(V2TIMMessage *obj1, V2TIMMessage *obj2) {
if ([obj1.timestamp timeIntervalSince1970] == [obj2.timestamp timeIntervalSince1970]) {
return obj1.seq > obj2.seq;
} else {
return [obj1.timestamp compare:obj2.timestamp];
}
}];
if (!merge) {
NSMutableArray *forwardMsgs = [NSMutableArray array];
for (V2TIMMessage *msg in msgs) {
V2TIMMessage *forwardMessage = [V2TIMManager.sharedInstance createForwardMessage:msg];
if (forwardMessage) {
forwardMessage.isExcludedFromUnreadCount = [TUIConfig defaultConfig].isExcludedFromUnreadCount;
forwardMessage.isExcludedFromLastMessage = [TUIConfig defaultConfig].isExcludedFromLastMessage;
[forwardMsgs addObject:forwardMessage];
}
}
if (resultBlock) {
resultBlock(convCellData, forwardMsgs);
}
return;
}
@weakify(self);
NSString *loginUserId = [V2TIMManager.sharedInstance getLoginUser];
[V2TIMManager.sharedInstance getUsersInfo:@[ loginUserId ]
succ:^(NSArray<V2TIMUserFullInfo *> *infoList) {
@strongify(self);
NSString *myName = loginUserId;
if (infoList.firstObject.nickName.length > 0) {
myName = infoList.firstObject.nickName;
}
NSString *title = [self.delegate dataProvider:self mergeForwardTitleWithMyName:myName];
NSMutableArray *abstactList = [NSMutableArray array];
if (uiMsgs.count > 0) {
[abstactList addObject:[self abstractDisplayWithMessage:msgs[0]]];
}
if (uiMsgs.count > 1) {
[abstactList addObject:[self abstractDisplayWithMessage:msgs[1]]];
}
if (uiMsgs.count > 2) {
[abstactList addObject:[self abstractDisplayWithMessage:msgs[2]]];
}
NSString *compatibleText = TIMCommonLocalizableString(TUIKitRelayCompatibleText);
V2TIMMessage *mergeMessage = [V2TIMManager.sharedInstance createMergerMessage:msgs
title:title
abstractList:abstactList
compatibleText:compatibleText];
if (mergeMessage == nil) {
if (fail) {
fail(ERR_NO_SUCC_RESULT, @"failed to merge-forward");
}
return;
}
mergeMessage.isExcludedFromUnreadCount = [TUIConfig defaultConfig].isExcludedFromUnreadCount;
mergeMessage.isExcludedFromLastMessage = [TUIConfig defaultConfig].isExcludedFromLastMessage;
if (resultBlock) {
resultBlock(convCellData, @[ mergeMessage ]);
}
}
fail:fail];
});
}
- (NSString *)abstractDisplayWithMessage:(V2TIMMessage *)msg {
return nil;
}
@end
#pragma mark - TUIChatBaseDataProvider (IMSDK)
@implementation TUIChatBaseDataProvider (IMSDK)
+ (void)getTotalUnreadMessageCountWithSuccBlock:(void (^)(UInt64 totalCount))succ fail:(nullable V2TIMFail)fail {
[V2TIMManager.sharedInstance getTotalUnreadMessageCount:succ fail:fail];
}
+ (void)saveDraftWithConversationID:(NSString *)conversationId Text:(NSString *)text {
NSString *draft = text;
draft = [draft stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
[[V2TIMManager sharedInstance] setConversationDraft:conversationId draftText:draft succ:nil fail:nil];
}
#pragma mark - C2C
+ (void)getFriendInfoWithUserId:(nullable NSString *)userID
SuccBlock:(void (^)(V2TIMFriendInfoResult *friendInfoResult))succ
failBlock:(nullable V2TIMFail)fail {
NSParameterAssert(userID);
if (fail && !userID) {
fail(ERR_INVALID_PARAMETERS, @"userID is nil");
return;
}
[[V2TIMManager sharedInstance] getFriendsInfo:@[ userID ]
succ:^(NSArray<V2TIMFriendInfoResult *> *resultList) {
V2TIMFriendInfoResult *result = resultList.firstObject;
succ(result);
}
fail:fail];
}
+ (void)getUserInfoWithUserId:(NSString *)userID SuccBlock:(void (^)(V2TIMUserFullInfo *userInfo))succ failBlock:(nullable V2TIMFail)fail {
NSParameterAssert(userID);
if (!userID) {
if (fail) {
fail(ERR_INVALID_PARAMETERS, @"userID is nil");
}
return;
}
[[V2TIMManager sharedInstance] getUsersInfo:@[ userID ]
succ:^(NSArray<V2TIMUserFullInfo *> *infoList) {
V2TIMUserFullInfo *info = infoList.firstObject;
if (succ) {
succ(info);
}
}
fail:fail];
}
#pragma mark - Group
+ (void)getGroupInfoWithGroupID:(NSString *)groupID SuccBlock:(void (^)(V2TIMGroupInfoResult *groupResult))succ failBlock:(nullable V2TIMFail)fail {
NSParameterAssert(groupID);
if (fail && !groupID) {
fail(ERR_INVALID_PARAMETERS, @"groupID is nil");
return;
}
[[V2TIMManager sharedInstance] getGroupsInfo:@[ groupID ]
succ:^(NSArray<V2TIMGroupInfoResult *> *groupResultList) {
V2TIMGroupInfoResult *result = groupResultList.firstObject;
if (result && result.resultCode == 0) {
if (succ) {
succ(result);
}
} else {
if (fail) {
fail(result.resultCode, result.resultMsg);
}
}
}
fail:fail];
}
+ (void)findMessages:(NSArray *)msgIDs callback:(void (^)(BOOL succ, NSString *error_message, NSArray *msgs))callback {
[V2TIMManager.sharedInstance findMessages:msgIDs
succ:^(NSArray<V2TIMMessage *> *msgs) {
if (callback) {
callback(YES, nil, msgs);
}
}
fail:^(int code, NSString *desc) {
if (callback) {
callback(NO, desc, @[]);
}
}];
}
+ (void)insertLocalTipsMessage:(NSString *)content chatID:(NSString *)chatID isGroup:(BOOL)isGroup {
NSDictionary *dic = @{
@"version" : @(1),
BussinessID : @"local_tips",
@"content" : content.length>0?content:@""
};
NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
V2TIMMessage *msg = [[V2TIMManager sharedInstance] createCustomMessage:data];
if (msg == nil) {
return;
}
NSString *messageID = nil;
NSString *senderID = [TUILogin getUserID];
if (isGroup) {
NSString *groupID = chatID.length>0?chatID:@"";
messageID = [V2TIMManager.sharedInstance insertGroupMessageToLocalStorage:msg to:groupID sender:senderID succ:^{
NSDictionary *userInfo = @{@"message" : msg,@"needScrollToBottom":@"1"};
[[NSNotificationCenter defaultCenter] postNotificationName:TUIChatInsertMessageWithoutUpdateUINotification object:nil userInfo:userInfo];
} fail:^(int code, NSString *desc) {
}];
}
else {
NSString *userID = chatID.length>0?chatID:@"";
messageID = [V2TIMManager.sharedInstance insertC2CMessageToLocalStorage:msg to:userID sender:senderID succ:^{
NSDictionary *userInfo = @{@"message" : msg,@"needScrollToBottom":@"1"};
[[NSNotificationCenter defaultCenter] postNotificationName:TUIChatInsertMessageWithoutUpdateUINotification object:nil userInfo:userInfo];
} fail:^(int code, NSString *desc) {
}];
}
}
@end

View File

@@ -0,0 +1,153 @@
//
// TUIChatCallingDataProvider.h
// TUIChat
//
// Created by harvy on 2022/12/21.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <ImSDK_Plus/ImSDK_Plus.h>
@class TUIMessageCellData;
NS_ASSUME_NONNULL_BEGIN
/**
* The protocol type of calls
*/
typedef NS_ENUM(NSInteger, TUICallProtocolType) {
TUICallProtocolTypeUnknown = 0,
TUICallProtocolTypeSend = 1,
TUICallProtocolTypeAccept = 2,
TUICallProtocolTypeReject = 3,
TUICallProtocolTypeCancel = 4,
TUICallProtocolTypeHangup = 5,
TUICallProtocolTypeTimeout = 6,
TUICallProtocolTypeLineBusy = 7,
TUICallProtocolTypeSwitchToAudio = 8,
TUICallProtocolTypeSwitchToAudioConfirm = 9,
};
/**
* The stream media type of calls
*/
typedef NS_ENUM(NSInteger, TUICallStreamMediaType) {
TUICallStreamMediaTypeUnknown = 0,
TUICallStreamMediaTypeVoice = 1,
TUICallStreamMediaTypeVideo = 2,
};
/**
* The participant style of calls
*/
typedef NS_ENUM(NSInteger, TUICallParticipantType) {
TUICallParticipantTypeUnknown = 0,
TUICallParticipantTypeC2C = 1,
TUICallParticipantTypeGroup = 2,
};
/**
* The role of participant
*/
typedef NS_ENUM(NSInteger, TUICallParticipantRole) {
TUICallParticipantRoleUnknown = 0,
TUICallParticipantRoleCaller = 1,
TUICallParticipantRoleCallee = 2,
};
/**
* The direction of voice-video-call message
*/
typedef NS_ENUM(NSInteger, TUICallMessageDirection) {
TUICallMessageDirectionIncoming = 0,
TUICallMessageDirectionOutgoing = 1,
};
@protocol TUIChatCallingInfoProtocol <NSObject>
/**
* The protocol type of voice-video-call
*/
@property(nonatomic, assign, readonly) TUICallProtocolType protocolType;
/**
* The stream media type of voice-video-call
*/
@property(nonatomic, assign, readonly) TUICallStreamMediaType streamMediaType;
/**
* The participate type of voice-video-call, one-to-one and group are supported
*/
@property(nonatomic, assign, readonly) TUICallParticipantType participantType;
/**
* The participate role type of voice-video-call, caller and callee are supported
*/
@property(nonatomic, assign, readonly) TUICallParticipantRole participantRole;
/**
* Exclude from history of chat pagesupported in TUIChat 7.1 and later
*/
@property(nonatomic, assign, readonly) BOOL excludeFromHistory;
/**
* The display text of voice-video-call message
*/
@property(nonatomic, copy, readonly, nonnull) NSString *content;
/**
*
* The display direction of voice-video-call message
*/
@property(nonatomic, assign, readonly) TUICallMessageDirection direction;
/**
*
* Whether display unread point in call history
*/
@property(nonatomic, assign, readonly) BOOL showUnreadPoint;
/**
* Whether to use the receiver's avatar
*/
@property(nonatomic, assign, readonly) BOOL isUseReceiverAvatar;
@property(nonatomic, strong, readonly) NSArray<NSString *> *participantIDList;
@end
/**
* The style of voice-video-call message in TUIChat
*/
typedef NS_ENUM(NSInteger, TUIChatCallingMessageAppearance) {
TUIChatCallingMessageAppearanceDetails = 0,
TUIChatCallingMessageAppearanceSimplify = 1,
};
@protocol TUIChatCallingDataProtocol <NSObject>
/**
* Seting styles of voice-video-call message in TUIChat
*/
- (void)setCallingMessageStyle:(TUIChatCallingMessageAppearance)style;
/**
* Redial based on the current voice-video-call message (generally used to redial after clicking the call history on the chat page)
*/
- (void)redialFromMessage:(V2TIMMessage *)innerMessage;
/**
* Parse voice-video-call message
*/
- (BOOL)isCallingMessage:(V2TIMMessage *)innerMessage callingInfo:(id<TUIChatCallingInfoProtocol> __nullable *__nullable)callingInfo;
@end
@interface TUIChatCallingDataProvider : NSObject <TUIChatCallingDataProtocol>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,551 @@
//
// TUIChatCallingDataProvider.m
// TUIChat
//
// Created by harvy on 2022/12/21.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIChatCallingDataProvider.h"
#import <TIMCommon/TIMDefine.h>
#import <TUICore/TUICore.h>
#import <TUICore/TUILogin.h>
#import "TUIMessageBaseDataProvider.h"
typedef NSString *TUIChatMessageID;
typedef NSDictionary *TUIChatCallingJsonData;
// ********************************************************************
// TUIChatCallingInfo
// ********************************************************************
@interface TUIChatCallingInfo : NSObject <TUIChatCallingInfoProtocol>
@property(nonatomic, strong) TUIChatMessageID msgID;
@property(nonatomic, strong, nullable) TUIChatCallingJsonData jsonData;
@property(nonatomic, strong, nullable) V2TIMSignalingInfo *signalingInfo;
@property(nonatomic, strong, nullable) V2TIMMessage *innerMessage;
@property(nonatomic, assign) TUIChatCallingMessageAppearance style;
@end
@implementation TUIChatCallingInfo
#pragma mark - TUIChatCallingInfoProtocol
- (TUICallProtocolType)protocolType {
if (self.jsonData == nil || self.signalingInfo == nil || self.innerMessage == nil) {
return TUICallProtocolTypeUnknown;
}
TUICallProtocolType type = TUICallProtocolTypeUnknown;
switch (self.signalingInfo.actionType) {
case SignalingActionType_Invite: {
NSDictionary *data = [self.jsonData objectForKey:@"data"];
if (data && [data isKindOfClass:NSDictionary.class]) {
// New version for calling
NSString *cmd = [data objectForKey:@"cmd"];
if ([cmd isKindOfClass:NSString.class]) {
if ([cmd isEqualToString:@"switchToAudio"]) {
type = TUICallProtocolTypeSwitchToAudio;
} else if ([cmd isEqualToString:@"hangup"]) {
type = TUICallProtocolTypeHangup;
} else if ([cmd isEqualToString:@"videoCall"]) {
type = TUICallProtocolTypeSend;
} else if ([cmd isEqualToString:@"audioCall"]) {
type = TUICallProtocolTypeSend;
} else {
type = TUICallProtocolTypeUnknown;
}
} else {
NSLog(@"calling protocol error, %@", self.jsonData);
type = TUICallProtocolTypeUnknown;
}
} else {
// Compatiable
NSNumber *callEnd = [self.jsonData objectForKey:@"call_end"];
if (callEnd && [callEnd isKindOfClass:NSNumber.class]) {
type = TUICallProtocolTypeHangup;
} else {
type = TUICallProtocolTypeSend;
}
}
} break;
case SignalingActionType_Cancel_Invite: {
type = TUICallProtocolTypeCancel;
} break;
case SignalingActionType_Accept_Invite: {
NSDictionary *data = [self.jsonData objectForKey:@"data"];
if (data && [data isKindOfClass:NSDictionary.class]) {
// New version for calling
NSString *cmd = [data objectForKey:@"cmd"];
if ([cmd isKindOfClass:NSString.class]) {
if ([cmd isEqualToString:@"switchToAudio"]) {
type = TUICallProtocolTypeSwitchToAudioConfirm;
} else {
type = TUICallProtocolTypeAccept;
}
} else {
NSLog(@"calling protocol error, %@", self.jsonData);
type = TUICallProtocolTypeAccept;
}
} else {
// Compatiable
type = TUICallProtocolTypeAccept;
}
} break;
case SignalingActionType_Reject_Invite: {
if ([self.jsonData objectForKey:@"line_busy"]) {
type = TUICallProtocolTypeLineBusy;
} else {
type = TUICallProtocolTypeReject;
}
} break;
case SignalingActionType_Invite_Timeout: {
type = TUICallProtocolTypeTimeout;
} break;
default:
type = TUICallProtocolTypeUnknown;
break;
}
return type;
}
- (TUICallStreamMediaType)streamMediaType {
TUICallProtocolType protocolType = self.protocolType;
if (protocolType == TUICallProtocolTypeUnknown) {
return TUICallStreamMediaTypeUnknown;
}
// Default type
TUICallStreamMediaType type = TUICallStreamMediaTypeUnknown;
NSNumber *callType = [self.jsonData objectForKey:@"call_type"];
if (callType && [callType isKindOfClass:NSNumber.class]) {
if ([callType integerValue] == 1) {
type = TUICallStreamMediaTypeVoice;
} else if ([callType integerValue] == 2) {
type = TUICallStreamMediaTypeVideo;
}
}
// Read from special protocol
if (protocolType == TUICallProtocolTypeSend) {
NSDictionary *data = [self.jsonData objectForKey:@"data"];
if (data && [data isKindOfClass:NSDictionary.class]) {
NSString *cmd = [data objectForKey:@"cmd"];
if ([cmd isEqual:@"audioCall"]) {
type = TUICallStreamMediaTypeVoice;
} else if ([cmd isEqual:@"videoCall"]) {
type = TUICallStreamMediaTypeVideo;
}
}
} else if (protocolType == TUICallProtocolTypeSwitchToAudio || protocolType == TUICallProtocolTypeSwitchToAudioConfirm) {
type = TUICallStreamMediaTypeVideo;
}
return type;
}
- (TUICallParticipantType)participantType {
if (self.protocolType == TUICallProtocolTypeUnknown) {
return TUICallParticipantTypeUnknown;
}
if (self.signalingInfo.groupID.length > 0) {
return TUICallParticipantTypeGroup;
} else {
return TUICallParticipantTypeC2C;
}
}
- (NSString *)caller {
NSString *callerID = nil;
NSDictionary *data = [self.jsonData objectForKey:@"data"];
if (data && [data isKindOfClass:NSDictionary.class]) {
NSString *inviter = [data objectForKey:@"inviter"];
if (inviter && [inviter isKindOfClass:NSString.class]) {
callerID = inviter;
}
}
if (callerID == nil) {
callerID = TUILogin.getUserID;
}
return callerID;
}
- (TUICallParticipantRole)participantRole {
if ([self.caller isEqualToString:TUILogin.getUserID]) {
return TUICallParticipantRoleCaller;
} else {
return TUICallParticipantRoleCallee;
}
}
- (BOOL)excludeFromHistory {
if (self.style == TUIChatCallingMessageAppearanceSimplify) {
return self.protocolType != TUICallProtocolTypeUnknown && self.innerMessage.isExcludedFromLastMessage && self.innerMessage.isExcludedFromUnreadCount;
} else {
return NO;
}
}
- (NSString *)content {
if (self.style == TUIChatCallingMessageAppearanceSimplify) {
return [self contentForSimplifyAppearance];
} else {
return [self contentForDetailsAppearance];
}
}
- (TUICallMessageDirection)direction {
if (self.style == TUIChatCallingMessageAppearanceSimplify) {
return [self directionForSimplifyAppearance];
} else {
return [self directionForDetailsAppearance];
}
}
- (BOOL)showUnreadPoint {
if (self.excludeFromHistory) {
return NO;
}
return (self.innerMessage.localCustomInt == 0) && (self.participantRole == TUICallParticipantRoleCallee) &&
(self.participantType == TUICallParticipantTypeC2C) &&
(self.protocolType == TUICallProtocolTypeCancel || self.protocolType == TUICallProtocolTypeTimeout ||
self.protocolType == TUICallProtocolTypeLineBusy);
}
- (BOOL)isUseReceiverAvatar {
if (self.style == TUIChatCallingMessageAppearanceSimplify) {
return [self isUseReceiverAvatarForSimplifyAppearance];
} else {
return [self isUseReceiverAvatarForDetailsAppearance];
}
}
- (NSArray<NSString *> *)participantIDList {
NSMutableArray *arrayM = [NSMutableArray array];
if (self.signalingInfo.inviter) {
[arrayM addObject:self.signalingInfo.inviter];
}
if (self.signalingInfo.inviteeList.count > 0) {
[arrayM addObjectsFromArray:self.signalingInfo.inviteeList];
}
return [NSArray arrayWithArray:arrayM];
}
#pragma mark - Details style
- (NSString *)contentForDetailsAppearance {
TUICallProtocolType protocolType = self.protocolType;
BOOL isGroup = (self.participantType == TUICallParticipantTypeGroup);
if (protocolType == TUICallProtocolTypeUnknown) {
return TIMCommonLocalizableString(TUIkitSignalingUnrecognlize);
}
NSString *display = TIMCommonLocalizableString(TUIkitSignalingUnrecognlize);
NSString *showName = [TUIMessageBaseDataProvider getShowName:self.innerMessage];
if (protocolType == TUICallProtocolTypeSend) {
// Launch call
display = isGroup ? [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitSignalingNewGroupCallFormat), showName]
: TIMCommonLocalizableString(TUIKitSignalingNewCall);
} else if (protocolType == TUICallProtocolTypeAccept) {
// Accept call
display = isGroup ? [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitSignalingHangonCallFormat), showName]
: TIMCommonLocalizableString(TUIkitSignalingHangonCall);
} else if (protocolType == TUICallProtocolTypeReject) {
// Reject call
display = isGroup ? [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitSignalingDeclineFormat), showName]
: TIMCommonLocalizableString(TUIkitSignalingDecline);
} else if (protocolType == TUICallProtocolTypeCancel) {
// Cancel pending call
display = isGroup ? [NSString stringWithFormat:TIMCommonLocalizableString(TUIkitSignalingCancelGroupCallFormat), showName]
: TIMCommonLocalizableString(TUIkitSignalingCancelCall);
} else if (protocolType == TUICallProtocolTypeHangup) {
// Hang up
NSUInteger duration = [[self.jsonData objectForKey:@"call_end"] unsignedIntegerValue];
display = isGroup
? TIMCommonLocalizableString(TUIKitSignalingFinishGroupChat)
: [NSString stringWithFormat:@"%@:%.2d:%.2d",TIMCommonLocalizableString(TUIKitSignalingFinishConversationAndTimeFormat),duration / 60, duration % 60];
} else if (protocolType == TUICallProtocolTypeTimeout) {
// Call timeout
NSMutableString *mutableContent = [NSMutableString string];
if (isGroup) {
for (NSString *invitee in self.signalingInfo.inviteeList) {
[mutableContent appendString:@"\"{"];
[mutableContent appendString:invitee];
[mutableContent appendString:@"}\""];
}
if (mutableContent.length > 0) {
[mutableContent replaceCharactersInRange:NSMakeRange(mutableContent.length - 1, 1) withString:@" "];
}
}
[mutableContent appendString:TIMCommonLocalizableString(TUIKitSignalingNoResponse)];
display = [NSString stringWithString:mutableContent];
} else if (protocolType == TUICallProtocolTypeLineBusy) {
// Hang up with line busy
display = isGroup ? [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitSignalingBusyFormat), showName]
: TIMCommonLocalizableString(TUIKitSignalingCallBusy);
} else if (protocolType == TUICallProtocolTypeSwitchToAudio) {
// Change video-call to voice-call
display = TIMCommonLocalizableString(TUIKitSignalingSwitchToAudio);
} else if (protocolType == TUICallProtocolTypeSwitchToAudioConfirm) {
// Confirm the change of video-voice-call
display = TIMCommonLocalizableString(TUIKitSignalingComfirmSwitchToAudio);
}
return rtlString(display);
}
- (TUICallMessageDirection)directionForDetailsAppearance {
if (self.innerMessage.isSelf) {
return TUICallMessageDirectionOutgoing;
} else {
return TUICallMessageDirectionIncoming;
}
}
- (BOOL)isUseReceiverAvatarForDetailsAppearance {
return NO;
}
#pragma mark - Simplify style
- (NSString *)contentForSimplifyAppearance {
if (self.excludeFromHistory) {
return nil;
}
TUICallParticipantType participantType = self.participantType;
TUICallProtocolType protocolType = self.protocolType;
BOOL isCaller = (self.participantRole == TUICallParticipantRoleCaller);
NSString *display = nil;
NSString *showName = [TUIMessageBaseDataProvider getShowName:self.innerMessage];
if (participantType == TUICallParticipantTypeC2C) {
// C2C shown: rejectcancelhanguptimeoutline_busy
if (protocolType == TUICallProtocolTypeReject) {
display = isCaller ? TUIChatLocalizableString(TUIChatCallRejectInCaller) : TUIChatLocalizableString(TUIChatCallRejectInCallee);
} else if (protocolType == TUICallProtocolTypeCancel) {
display = isCaller ? TUIChatLocalizableString(TUIChatCallCancelInCaller) : TUIChatLocalizableString(TUIChatCallCancelInCallee);
} else if (protocolType == TUICallProtocolTypeHangup) {
NSInteger duration = [[self.jsonData objectForKey:@"call_end"] integerValue];
display = [NSString stringWithFormat:@"%@:%.2d:%.2d",TUIChatLocalizableString(TUIChatCallDurationFormat),duration / 60, duration % 60];
} else if (protocolType == TUICallProtocolTypeTimeout) {
display = isCaller ? TUIChatLocalizableString(TUIChatCallTimeoutInCaller) : TUIChatLocalizableString(TUIChatCallTimeoutInCallee);
} else if (protocolType == TUICallProtocolTypeLineBusy) {
display = isCaller ? TUIChatLocalizableString(TUIChatCallLinebusyInCaller) : TUIChatLocalizableString(TUIChatCallLinebusyInCallee);
}
// C2C compatiable
else if (protocolType == TUICallProtocolTypeSend) {
display = TUIChatLocalizableString(TUIChatCallSend);
} else if (protocolType == TUICallProtocolTypeAccept) {
display = TUIChatLocalizableString(TUIChatCallAccept);
} else if (protocolType == TUICallProtocolTypeSwitchToAudio) {
display = TUIChatLocalizableString(TUIChatCallSwitchToAudio);
} else if (protocolType == TUICallProtocolTypeSwitchToAudioConfirm) {
display = TUIChatLocalizableString(TUIChatCallConfirmSwitchToAudio);
} else {
display = TUIChatLocalizableString(TUIChatCallUnrecognized);
}
} else if (participantType == TUICallParticipantTypeGroup) {
// Group shown: invitecancelhanguptimeoutline_busy
if (protocolType == TUICallProtocolTypeSend) {
display = [NSString stringWithFormat:TUIChatLocalizableString(TUIChatGroupCallSendFormat), showName];
} else if (protocolType == TUICallProtocolTypeCancel) {
display = TUIChatLocalizableString(TUIChatGroupCallEnd);
} else if (protocolType == TUICallProtocolTypeHangup) {
display = TUIChatLocalizableString(TUIChatGroupCallEnd);
} else if (protocolType == TUICallProtocolTypeTimeout || protocolType == TUICallProtocolTypeLineBusy) {
NSMutableString *mutableContent = [NSMutableString string];
if (participantType == TUICallParticipantTypeGroup) {
for (NSString *invitee in self.signalingInfo.inviteeList) {
[mutableContent appendString:@"\"{"];
[mutableContent appendString:invitee];
[mutableContent appendString:@"}\""];
}
[mutableContent replaceCharactersInRange:NSMakeRange(mutableContent.length - 1, 1) withString:@" "];
}
if (protocolType == TUICallProtocolTypeLineBusy) {
[mutableContent appendString:TUIChatLocalizableString(TUIChatCallLinebusyInCallee)];
} else {
[mutableContent appendString:TUIChatLocalizableString(TUIChatGroupCallNoAnswer)];
}
display = [NSString stringWithString:mutableContent];
}
// Group compatiable
else if (protocolType == TUICallProtocolTypeReject) {
display = [NSString stringWithFormat:TUIChatLocalizableString(TUIChatGroupCallRejectFormat), showName];
} else if (protocolType == TUICallProtocolTypeAccept) {
display = [NSString stringWithFormat:TUIChatLocalizableString(TUIChatGroupCallAcceptFormat), showName];
} else if (protocolType == TUICallProtocolTypeSwitchToAudio) {
display = [NSString stringWithFormat:TUIChatLocalizableString(TUIChatGroupCallSwitchToAudioFormat), showName];
} else if (protocolType == TUICallProtocolTypeSwitchToAudioConfirm) {
display = [NSString stringWithFormat:TUIChatLocalizableString(TUIChatGroupCallConfirmSwitchToAudioFormat), showName];
} else {
display = TUIChatLocalizableString(TUIChatCallUnrecognized);
}
} else {
display = TUIChatLocalizableString(TUIChatCallUnrecognized);
}
return rtlString(display);
}
- (TUICallMessageDirection)directionForSimplifyAppearance {
if (self.participantRole == TUICallParticipantRoleCaller) {
return TUICallMessageDirectionOutgoing;
} else {
return TUICallMessageDirectionIncoming;
}
}
- (BOOL)isUseReceiverAvatarForSimplifyAppearance {
if (self.direction == TUICallMessageDirectionOutgoing) {
return !self.innerMessage.isSelf;
} else {
return self.innerMessage.isSelf;
}
}
#pragma mark - Utils
- (NSString *)convertProtocolTypeToString:(TUICallProtocolType)type {
static NSDictionary *dict = nil;
if (dict == nil) {
dict = @{
@(TUICallProtocolTypeSend) : @"TUICallProtocolTypeSend",
@(TUICallProtocolTypeAccept) : @"TUICallProtocolTypeAccept",
@(TUICallProtocolTypeReject) : @"TUICallProtocolTypeReject",
@(TUICallProtocolTypeCancel) : @"TUICallProtocolTypeCancel",
@(TUICallProtocolTypeHangup) : @"TUICallProtocolTypeHangup",
@(TUICallProtocolTypeTimeout) : @"TUICallProtocolTypeTimeout",
@(TUICallProtocolTypeLineBusy) : @"TUICallProtocolTypeLineBusy",
@(TUICallProtocolTypeSwitchToAudio) : @"TUICallProtocolTypeSwitchToAudio",
@(TUICallProtocolTypeSwitchToAudioConfirm) : @"TUICallProtocolTypeSwitchToAudioConfirm",
};
}
return [dict objectForKey:@(type)] ?: @"unknown";
}
@end
// ********************************************************************
// ********************************************************************
// TUIChatCallingDataProvider
// ********************************************************************
@interface TUIChatCallingDataProvider ()
@property(nonatomic, assign) TUIChatCallingMessageAppearance style;
@property(nonatomic, strong) NSCache<TUIChatMessageID, TUIChatCallingInfo *> *callingCache;
@end
@implementation TUIChatCallingDataProvider
- (instancetype)init {
if (self = [super init]) {
self.style = TUIChatCallingMessageAppearanceSimplify;
}
return self;
}
- (void)setCallingMessageStyle:(TUIChatCallingMessageAppearance)style {
self.style = style;
}
- (void)redialFromMessage:(V2TIMMessage *)innerMessage {
NSDictionary *param = nil;
id<TUIChatCallingInfoProtocol> callingInfo = nil;
if ([self isCallingMessage:innerMessage callingInfo:&callingInfo]) {
if (callingInfo.streamMediaType == TUICallStreamMediaTypeVoice) {
param = @{
TUICore_TUICallingService_ShowCallingViewMethod_UserIDsKey : @[ innerMessage.userID ],
TUICore_TUICallingService_ShowCallingViewMethod_CallTypeKey : @"0"
};
} else if (callingInfo.streamMediaType == TUICallStreamMediaTypeVideo) {
param = @{
TUICore_TUICallingService_ShowCallingViewMethod_UserIDsKey : @[ innerMessage.userID ],
TUICore_TUICallingService_ShowCallingViewMethod_CallTypeKey : @"1"
};
}
if (param) {
[TUICore callService:TUICore_TUICallingService method:TUICore_TUICallingService_ShowCallingViewMethod param:param];
}
}
}
- (BOOL)isCallingMessage:(V2TIMMessage *)innerMessage callingInfo:(id<TUIChatCallingInfoProtocol> __nullable *__nullable)callingInfo {
TUIChatCallingInfo *item = [self callingInfoForMesssage:innerMessage];
if (item == nil) {
if (callingInfo) {
*callingInfo = nil;
}
return NO;
} else {
if (callingInfo) {
*callingInfo = item;
}
return YES;
}
}
- (TUIChatCallingInfo *__nullable)callingInfoForMesssage:(V2TIMMessage *)innerMessage {
// 1. Fetch from cache
TUIChatMessageID msgID = innerMessage.msgID ?: @"";
TUIChatCallingInfo *item = [self.callingCache objectForKey:msgID];
if (item) {
item.innerMessage = innerMessage;
return item;
}
// 2. Parse
V2TIMSignalingInfo *info = [V2TIMManager.sharedInstance getSignallingInfo:innerMessage];
if (info == nil || info.data.length == 0) {
return nil;
}
NSData *data = [info.data dataUsingEncoding:NSUTF8StringEncoding];
if (data == nil) {
return nil;
}
NSError *err = nil;
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&err];
if (param == nil || ![param isKindOfClass:NSDictionary.class]) {
return nil;
}
NSString *businessID = [param objectForKey:@"businessID"];
if (businessID == nil || ![businessID isKindOfClass:NSString.class]) {
return nil;
}
if (![businessID isEqualToString:@"av_call"] && ![businessID isEqualToString:@"rtc_call"] ) {
return nil;
}
// 3 Cached and return
item = [[TUIChatCallingInfo alloc] init];
item.style = self.style;
item.signalingInfo = info;
item.jsonData = param;
item.innerMessage = innerMessage;
[self.callingCache setObject:item forKey:msgID];
return item;
}
#pragma mark - Lazy
- (NSCache<TUIChatMessageID, TUIChatCallingInfo *> *)callingCache {
if (_callingCache == nil) {
_callingCache = [[NSCache alloc] init];
}
return _callingCache;
}
@end
// ********************************************************************

View File

@@ -0,0 +1,78 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
#import <Foundation/Foundation.h>
@class TUIGroupPendencyCellData;
NS_ASSUME_NONNULL_BEGIN
/**
*
* 【Module name】 TUIGroupPendencyViewModel
* 【Function description】Group request view model
* This view model is responsible for unified processing of group request messages. For example, a series of logics such as obtaining the unread count,
* receiving new request messages and updating them, agreeing/removing data in the existing data queue, etc.
*/
@interface TUIGroupPendencyDataProvider : NSObject
/**
* Request data list
* The object type stored in this list is TUIGroupPendencyCellData.
* That is, this array stores all pending request data of the current group, and this attribute is read-only and cannot be modified.
*/
@property(readonly) NSArray *dataList;
/**
*
* Whether to have next request data.
* When loading data, hasNextData is YES when the request list of the current group has not been read out.
*/
@property BOOL hasNextData;
/**
*
* Loading identifier
* When the current view model is loading data, this property is YES. At this time, loading again is not allowed until the current loading process is
* completed.
*/
@property BOOL isLoading;
/**
*
* Unread count, that is, the number of outstanding requests for the current group.
*/
@property int unReadCnt;
/**
*
* group ID
* It is used to identify the current group and determine whether the request to join the group is a request for this group.
*/
@property NSString *groupId;
/**
*
* Load data
* 1. First determine whether it is currently loading, and if so, terminate this loading.
* 2. Pull the request data from the server through the getPendencyFromServer interface provided by the TIMGroupManager class in the IM SDK. The default is 100
* requests per page.
* 3. For the pulled data, determine whether the group ID corresponding to the request is the same as this group, and if so, convert the request to
* TUIGroupPendencyCellData and store it in the datalist. (The request object pulled from the server is TIMGroupPendencyItem).
*/
- (void)loadData;
/**
* Approve current request data.
* This function directly calls accept implemented in TUIGroupPendencyCellData, and the unread count is decremented by 1.
*/
- (void)acceptData:(TUIGroupPendencyCellData *)data;
/**
* Deny the current request data.
* Remove the data in the parameter from the datalist, and call reject implemented in TUIGroupPendencyCellData, while the unread count is decremented by 1.
*/
- (void)removeData:(TUIGroupPendencyCellData *)data;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,91 @@
//
// TUIGroupPendencyViewModel.m
// TXIMSDK_TUIKit_iOS
//
// Created by annidyfeng on 2019/6/18.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIGroupPendencyDataProvider.h"
#import <TIMCommon/TIMDefine.h>
@interface TUIGroupPendencyDataProvider ()
@property NSArray *dataList;
@property(nonatomic, assign) uint64_t origSeq;
@property(nonatomic, assign) uint64_t seq;
@property(nonatomic, assign) uint64_t timestamp;
@property(nonatomic, assign) uint64_t numPerPage;
@end
@implementation TUIGroupPendencyDataProvider
- (instancetype)init {
self = [super init];
_numPerPage = 100;
_dataList = @[];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onPendencyChanged:) name:TUIGroupPendencyCellData_onPendencyChanged object:nil];
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)onPendencyChanged:(NSNotification *)notification {
int unReadCnt = 0;
for (TUIGroupPendencyCellData *data in self.dataList) {
if (data.isRejectd || data.isAccepted) {
continue;
}
unReadCnt++;
}
self.unReadCnt = unReadCnt;
}
- (void)loadData {
if (self.isLoading) return;
self.isLoading = YES;
@weakify(self);
[[V2TIMManager sharedInstance]
getGroupApplicationList:^(V2TIMGroupApplicationResult *result) {
@strongify(self);
NSMutableArray *list = @[].mutableCopy;
for (V2TIMGroupApplication *item in result.applicationList) {
if ([item.groupID isEqualToString:self.groupId] && item.handleStatus == V2TIM_GROUP_APPLICATION_HANDLE_STATUS_UNHANDLED) {
TUIGroupPendencyCellData *data = [[TUIGroupPendencyCellData alloc] initWithPendency:item];
[list addObject:data];
}
}
self.dataList = list;
self.unReadCnt = (int)list.count;
self.isLoading = NO;
self.hasNextData = NO;
;
}
fail:nil];
}
- (void)acceptData:(TUIGroupPendencyCellData *)data {
[data accept];
self.unReadCnt--;
}
- (void)removeData:(TUIGroupPendencyCellData *)data {
NSMutableArray *dataList = [NSMutableArray arrayWithArray:self.dataList];
[dataList removeObject:data];
self.dataList = dataList;
[data reject];
self.unReadCnt--;
}
@end

View File

@@ -0,0 +1,28 @@
//
// TUIMessageDataProvider+ProtectedAPI.h
// TXIMSDK_TUIKit_iOS
//
// Created by kayev on 2021/7/9.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIMessageBaseDataProvider.h"
NS_ASSUME_NONNULL_BEGIN
@interface TUIMessageBaseDataProvider ()
@property(nonatomic) NSMutableArray<TUIMessageCellData *> *uiMsgs_;
@property(nonatomic) NSMutableDictionary<NSString *, NSNumber *> *heightCache_;
@property(nonatomic) BOOL isLoadingData;
@property(nonatomic) BOOL isNoMoreMsg;
@property(nonatomic) BOOL isFirstLoad;
@property(nonatomic) V2TIMMessage *msgForDate;
- (nullable TUIMessageCellData *)getSystemMsgFromDate:(NSDate *)date;
- (NSMutableArray *)transUIMsgFromIMMsg:(NSArray *)msgs;
- (void)onRecvNewMessage:(V2TIMMessage *)msg;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,249 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
#import <Foundation/Foundation.h>
#import <TIMCommon/TIMDefine.h>
#import <TIMCommon/TUIMessageCell.h>
#import <TIMCommon/TUIMessageCellData.h>
#import "TUIChatConversationModel.h"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, TUIMessageBaseDataProviderDataSourceChangeType) {
TUIMessageBaseDataProviderDataSourceChangeTypeInsert,
TUIMessageBaseDataProviderDataSourceChangeTypeDelete,
TUIMessageBaseDataProviderDataSourceChangeTypeReload,
};
@class TUIMessageBaseDataProvider;
@protocol TUIMessageBaseDataProviderDataSource <NSObject>
@required
- (void)dataProviderDataSourceWillChange:(TUIMessageBaseDataProvider *)dataProvider;
- (void)dataProviderDataSourceChange:(TUIMessageBaseDataProvider *)dataProvider
withType:(TUIMessageBaseDataProviderDataSourceChangeType)type
atIndex:(NSUInteger)index
animation:(BOOL)animation;
- (void)dataProviderDataSourceDidChange:(TUIMessageBaseDataProvider *)dataProvider;
- (void)dataProvider:(TUIMessageBaseDataProvider *)dataProvider onRemoveHeightCache:(TUIMessageCellData *)cellData;
@optional
/**
* Message read event
*
* @param userID recevier of one-to-one message
* @param timestamp Read receipt time, messages before this timestamp can be considered read by the other party
*/
- (void)dataProvider:(TUIMessageBaseDataProvider *)dataProvider ReceiveReadMsgWithUserID:(NSString *)userId Time:(time_t)timestamp;
/**
* Group message read event
*
* @param groupID Group ID
* @param msgID Message idenetifier
* @param readCount Count of read message
* @param unreadCount Count of unread message
*/
- (void)dataProvider:(TUIMessageBaseDataProvider *)dataProvider
ReceiveReadMsgWithGroupID:(NSString *)groupID
msgID:(NSString *)msgID
readCount:(NSUInteger)readCount
unreadCount:(NSUInteger)unreadCount;
/**
* A new message is received, the data has been changed, refreshed, it has been processed internally, and subsequent processing can be done in this method
*
* @param uiMsg The new message
*/
- (void)dataProvider:(TUIMessageBaseDataProvider *)dataProvider ReceiveNewUIMsg:(TUIMessageCellData *)uiMsg;
/**
*
* Reveived a recalled message
*/
- (void)dataProvider:(TUIMessageBaseDataProvider *)dataProvider ReceiveRevokeUIMsg:(TUIMessageCellData *)uiMsg;
/**
* This event is fired when a new message is received after the request for a new message is completed
* External can use this method to modify the CellData to be displayed, add messages (such as time messages), and customize messages
*/
- (nullable TUIMessageCellData *)dataProvider:(TUIMessageBaseDataProvider *)dataProvider CustomCellDataFromNewIMMessage:(V2TIMMessage *)msg;
- (BOOL)isDataSourceConsistent;
@end
/**
*
* 【Module name】Chat message list view model (TUIMessageDataProvider)
* 【Function description】Responsible for implementing the data processing and business logic of the message list in the chat page
* 1. The view model can pull the message list data from the server through the interface provided by the IM SDK, and load the data.
* 2. The view model can synchronously remove the message list data when the user needs to delete the session list.
*/
@interface TUIMessageBaseDataProvider : NSObject
@property(nonatomic, weak) id<TUIMessageBaseDataProviderDataSource> dataSource;
@property(nonatomic, strong, readonly) TUIChatConversationModel *conversationModel;
@property(nonatomic, strong, readonly) NSArray<TUIMessageCellData *> *uiMsgs;
@property(nonatomic, strong, readonly) NSDictionary<NSString *, NSNumber *> *heightCache;
@property(nonatomic, assign, readonly) BOOL isLoadingData;
@property(nonatomic, assign, readonly) BOOL isNoMoreMsg;
@property(nonatomic, assign, readonly) BOOL isFirstLoad;
/**
*
*
* If adjacent messages are sent by the same user, the messages will be merged for display.
*/
@property(nonatomic, assign) BOOL mergeAdjacentMsgsFromTheSameSender;
/**
*
* Count of per page, default is 20.
*/
@property(nonatomic, assign) NSInteger pageCount;
- (instancetype)initWithConversationModel:(TUIChatConversationModel *)conversationModel;
- (void)loadMessageSucceedBlock:(void (^)(BOOL isFirstLoad, BOOL isNoMoreMsg, NSArray<TUIMessageCellData *> *newMsgs))succeedBlock
FailBlock:(V2TIMFail)failBlock;
- (void)sendUIMsg:(TUIMessageCellData *)uiMsg
toConversation:(TUIChatConversationModel *)conversationData
willSendBlock:(void (^)(BOOL isReSend, TUIMessageCellData *dateUIMsg))willSendBlock
SuccBlock:(nullable V2TIMSucc)succ
FailBlock:(nullable V2TIMFail)fail;
- (void)revokeUIMsg:(TUIMessageCellData *)uiMsg SuccBlock:(nullable V2TIMSucc)succ FailBlock:(nullable V2TIMFail)fail;
- (void)deleteUIMsgs:(NSArray<TUIMessageCellData *> *)uiMsgs SuccBlock:(nullable V2TIMSucc)succ FailBlock:(nullable V2TIMFail)fail;
- (void)addUIMsg:(TUIMessageCellData *)cellData;
- (void)removeUIMsg:(TUIMessageCellData *)cellData;
- (void)insertUIMsgs:(NSArray<TUIMessageCellData *> *)uiMsgs atIndexes:(NSIndexSet *)indexes;
- (void)sendPlaceHolderUIMessage:(TUIMessageCellData *)placeHolderCellData; //Only send PlaceHolder UI Message
- (void)addUIMsgs:(NSArray<TUIMessageCellData *> *)uiMsgs;
- (void)replaceUIMsg:(TUIMessageCellData *)cellData atIndex:(NSUInteger)index;
/**
* Preprocessing reply messages (asynchronously loading original messages and downloading corresponding thumbnails)
*/
- (void)preProcessMessage:(NSArray<TUIMessageCellData *> *)uiMsgs callback:(void (^)(void))callback;
- (NSArray<NSString *> *)getUserIDListForAdditionalUserInfo:(NSArray<TUIMessageCellData *> *)uiMsgs;
- (void)requestForAdditionalUserInfo:(NSArray<TUIMessageCellData *> *)uiMsgs callback:(void (^)(void))callback;
/**
* Send read receipts for latest messages
*/
- (void)sendLatestMessageReadReceipt;
/**
* Send a read receipt for the specified index message
*/
- (void)sendMessageReadReceiptAtIndexes:(NSArray *)indexes;
/**
* Get the index of the message in the mesage data through msgID
*/
- (NSInteger)getIndexOfMessage:(NSString *)msgID;
- (NSMutableArray *)transUIMsgFromIMMsg:(NSArray *)msgs;
- (void)clearUIMsgList;
- (void)processQuoteMessage:(NSArray<TUIMessageCellData *> *)uiMsgs; // subclass override required
+ (void)updateUIMsgStatus:(TUIMessageCellData *)cellData uiMsgs:(NSArray *)uiMsgs;
- (void)getPinMessageList;
- (void)loadGroupInfo:(dispatch_block_t)callback;
- (void)getSelfInfoInGroup:(dispatch_block_t)callback;
- (void)pinGroupMessage:(NSString *)groupID
message:(V2TIMMessage *)message
isPinned:(BOOL)isPinned
succ:(V2TIMSucc)succ
fail:(V2TIMFail)fail;
- (BOOL)isCurrentUserRoleSuperAdminInGroup;
- (BOOL)isCurrentMessagePin:(NSString *)msgID;
@property(nonatomic, copy) void (^groupRoleChanged)(V2TIMGroupMemberRole role);
@property(nonatomic, copy) void (^pinGroupMessageChanged)(NSArray *);
@end
@interface TUIMessageBaseDataProvider (IMSDK)
/// imsdk interface call
+ (NSString *)sendMessage:(V2TIMMessage *)message
toConversation:(TUIChatConversationModel *)conversationData
appendParams:(TUISendMessageAppendParams *)appendParams
Progress:(nullable V2TIMProgress)progress
SuccBlock:(nullable V2TIMSucc)succ
FailBlock:(nullable V2TIMFail)fail;
- (void)getLastMessage:(BOOL)isFromLocal succ:(void (^)(V2TIMMessage *message))succ fail:(V2TIMFail)fail;
+ (void)markC2CMessageAsRead:(NSString *)userID succ:(nullable V2TIMSucc)succ fail:(nullable V2TIMFail)fail;
+ (void)markGroupMessageAsRead:(NSString *)groupID succ:(nullable V2TIMSucc)succ fail:(nullable V2TIMFail)fail;
+ (void)markConversationAsUndead:(NSArray<NSString *> *)conversationIDList enableMark:(BOOL)enableMark;
+ (void)revokeMessage:(V2TIMMessage *)msg succ:(nullable V2TIMSucc)succ fail:(nullable V2TIMFail)fail;
+ (void)deleteMessages:(NSArray<V2TIMMessage *> *)msgList succ:(nullable V2TIMSucc)succ fail:(nullable V2TIMFail)fail;
+ (void)modifyMessage:(V2TIMMessage *)msg completion:(V2TIMMessageModifyCompletion)completion;
/**
* Send message read receipts
*/
+ (void)sendMessageReadReceipts:(NSArray *)msgs;
/**
* Getting the list of read and unread members of group messages
*/
+ (void)getReadMembersOfMessage:(V2TIMMessage *)msg
filter:(V2TIMGroupMessageReadMembersFilter)filter
nextSeq:(NSUInteger)nextSeq
completion:(void (^)(int code, NSString *desc, NSArray *members, NSUInteger nextSeq, BOOL isFinished))block;
/**
* Getting the read receipt of the message
*/
+ (void)getMessageReadReceipt:(NSArray *)messages succ:(nullable V2TIMMessageReadReceiptsSucc)succ fail:(nullable V2TIMFail)fail;
/// message -> cellData
+ (nullable TUIMessageCellData *)getCellData:(V2TIMMessage *)message;
+ (nullable TUIMessageCellData *)getSystemMsgFromDate:(NSDate *)date;
+ (nullable TUIMessageCellData *)getRevokeCellData:(V2TIMMessage *)message;
/// message -> displayString
+ (nullable NSString *)getDisplayString:(V2TIMMessage *)message;
+ (nullable NSString *)getRevokeDispayString:(V2TIMMessage *)message;
+ (nullable NSString *)getRevokeDispayString:(V2TIMMessage *)message operateUser:(V2TIMUserFullInfo *)operateUser reason:(NSString *)reason;
+ (nullable NSString *)getGroupTipsDisplayString:(V2TIMMessage *)message;
/// message <-> info
+ (V2TIMMessage *)getCustomMessageWithJsonData:(NSData *)data;
+ (V2TIMMessage *)getCustomMessageWithJsonData:(NSData *)data desc:(NSString *)desc extension:(NSString *)extension;
+ (NSMutableArray *)getUserIDList:(NSArray<V2TIMGroupMemberInfo *> *)infoList;
+ (NSString *)getShowName:(V2TIMMessage *)message;
+ (NSString *)getOpUserName:(V2TIMGroupMemberInfo *)info;
+ (NSMutableArray *)getUserNameList:(NSArray<V2TIMGroupMemberInfo *> *)infoList;
+ (NSString *)getUserName:(V2TIMGroupTipsElem *)tips with:(NSString *)userId;
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
#import "TUIMessageBaseDataProvider.h"
NS_ASSUME_NONNULL_BEGIN
@interface TUIMessageBaseMediaDataProvider : TUIMessageBaseDataProvider
@property(nonatomic, strong) NSMutableArray *medias;
- (instancetype)initWithConversationModel:(nullable TUIChatConversationModel *)conversationModel;
/**
* Pull 20 video (picture) messages before and after the current message
*/
- (void)loadMediaWithMessage:(V2TIMMessage *)curMessage;
/**
* Pull older 20 video (image) messages
*/
- (void)loadOlderMedia;
/**
* Pull the last 20 video (image) messages
*/
- (void)loadNewerMedia;
- (void)removeCache;
+ (TUIMessageCellData *)getMediaCellData:(V2TIMMessage *)message;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,229 @@
//
// TUIMessageSearchDataProvider.m
// TXIMSDK_TUIKit_iOS
//
// Created by kayev on 2021/7/8.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIMessageBaseMediaDataProvider.h"
#import "TUIMessageBaseDataProvider+ProtectedAPI.h"
/**
* Message pull method
*/
typedef NS_ENUM(NSInteger, TUIMediaLoadType) {
TUIMediaLoadType_Older = 1,
TUIMediaLoadType_Newer = 2,
TUIMediaLoadType_Older_And_Newer = 3,
};
@interface TUIMessageBaseMediaDataProvider ()
@property(nonatomic) TUIChatConversationModel *conversationModel;
@property(nonatomic, assign) TUIMediaLoadType loadType;
@property(nonatomic, strong) V2TIMMessage *loadMessage;
@property(nonatomic, assign) BOOL isOlderNoMoreMsg;
@property(nonatomic, assign) BOOL isNewerNoMoreMsg;
@end
@implementation TUIMessageBaseMediaDataProvider
- (instancetype)initWithConversationModel:(nullable TUIChatConversationModel *)conversationModel {
self = [super initWithConversationModel:conversationModel];
if (self) {
self.conversationModel = conversationModel;
self.isOlderNoMoreMsg = NO;
self.isNewerNoMoreMsg = NO;
self.pageCount = 20;
self.medias = [NSMutableArray array];
}
return self;
}
- (void)loadMediaWithMessage:(V2TIMMessage *)curMessage {
self.loadMessage = curMessage;
self.loadType = TUIMediaLoadType_Older_And_Newer;
/**
* When the message is being sent, an exception will occur when pulling the before and after video (picture) messages through the current message. Only the
* current message is displayed here for the time being.
*/
if (self.loadMessage.status != V2TIM_MSG_STATUS_SENDING) {
[self loadMedia];
} else {
NSMutableArray *medias = self.medias;
TUIMessageCellData *data = [self.class getMediaCellData:self.loadMessage];
if (data) {
[medias addObject:data];
self.medias = medias;
}
}
}
- (void)loadOlderMedia {
if (self.loadMessage.status != V2TIM_MSG_STATUS_SENDING) {
TUIMessageCellData *firstData = (TUIMessageCellData *)self.medias.firstObject;
self.loadMessage = firstData.innerMessage;
self.loadType = TUIMediaLoadType_Older;
[self loadMedia];
}
}
- (void)loadNewerMedia {
if (self.loadMessage.status != V2TIM_MSG_STATUS_SENDING) {
TUIMessageCellData *lastData = (TUIMessageCellData *)self.medias.lastObject;
self.loadMessage = lastData.innerMessage;
self.loadType = TUIMediaLoadType_Newer;
[self loadMedia];
}
}
- (void)loadMedia {
if (!self.loadMessage) {
return;
}
if (![self isNeedLoad:self.loadType]) {
return;
}
@weakify(self);
[self loadMediaMessage:self.loadMessage
loadType:self.loadType
SucceedBlock:^(NSArray<V2TIMMessage *> *_Nonnull olders, NSArray<V2TIMMessage *> *_Nonnull newers) {
@strongify(self);
NSMutableArray *medias = self.medias;
for (V2TIMMessage *msg in olders) {
TUIMessageCellData *data = [self.class getMediaCellData:msg];
if (data) {
[medias insertObject:data atIndex:0];
}
}
if (self.loadType == TUIMediaLoadType_Older_And_Newer) {
TUIMessageCellData *data = [self.class getMediaCellData:self.loadMessage];
if (data) {
[medias addObject:data];
;
}
}
for (V2TIMMessage *msg in newers) {
TUIMessageCellData *data = [self.class getMediaCellData:msg];
if (data) {
[medias addObject:data];
}
}
self.medias = medias;
}
FailBlock:^(int code, NSString *desc) {
NSLog(@"load message failed!");
}];
}
- (BOOL)isNeedLoad:(TUIMediaLoadType)type {
if ((TUIMediaLoadType_Older == type && self.isOlderNoMoreMsg) || (TUIMediaLoadType_Newer == type && self.isNewerNoMoreMsg) ||
(TUIMediaLoadType_Older_And_Newer == type && self.isOlderNoMoreMsg && self.isNewerNoMoreMsg)) {
return NO;
}
return YES;
}
- (void)loadMediaMessage:(V2TIMMessage *)loadMsg
loadType:(TUIMediaLoadType)type
SucceedBlock:(void (^)(NSArray<V2TIMMessage *> *_Nonnull olders, NSArray<V2TIMMessage *> *_Nonnull newers))succeedBlock
FailBlock:(V2TIMFail)failBlock {
if (self.isLoadingData) {
failBlock(ERR_SUCC, @"loading");
return;
}
self.isLoadingData = YES;
dispatch_group_t group = dispatch_group_create();
__block NSArray *olders = @[];
__block NSArray *newers = @[];
__block BOOL isOldLoadFail = NO;
__block BOOL isNewLoadFail = NO;
__block int failCode = 0;
__block NSString *failDesc = nil;
/**
* Loading the oldest 20 media messages starting from the positioning message
*/
if (TUIMediaLoadType_Older == type || TUIMediaLoadType_Older_And_Newer == type) {
dispatch_group_enter(group);
V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init];
option.getType = V2TIM_GET_LOCAL_OLDER_MSG;
option.count = self.pageCount;
option.groupID = self.conversationModel.groupID;
option.userID = self.conversationModel.userID;
option.lastMsg = loadMsg;
option.messageTypeList = @[ @(V2TIM_ELEM_TYPE_IMAGE), @(V2TIM_ELEM_TYPE_VIDEO) ];
[V2TIMManager.sharedInstance getHistoryMessageList:option
succ:^(NSArray<V2TIMMessage *> *msgs) {
olders = msgs ?: @[];
if (olders.count < self.pageCount) {
self.isOlderNoMoreMsg = YES;
}
dispatch_group_leave(group);
}
fail:^(int code, NSString *desc) {
isOldLoadFail = YES;
failCode = code;
failDesc = desc;
dispatch_group_leave(group);
}];
}
/**
* Load the latest 20 rich media messages starting from the positioning message
*/
if (TUIMediaLoadType_Newer == type || TUIMediaLoadType_Older_And_Newer == type) {
dispatch_group_enter(group);
V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init];
option.getType = V2TIM_GET_LOCAL_NEWER_MSG;
option.count = self.pageCount;
option.groupID = self.conversationModel.groupID;
option.userID = self.conversationModel.userID;
option.lastMsg = loadMsg;
option.messageTypeList = @[ @(V2TIM_ELEM_TYPE_IMAGE), @(V2TIM_ELEM_TYPE_VIDEO) ];
[V2TIMManager.sharedInstance getHistoryMessageList:option
succ:^(NSArray<V2TIMMessage *> *msgs) {
newers = msgs ?: @[];
if (newers.count < self.pageCount) {
self.isNewerNoMoreMsg = YES;
}
dispatch_group_leave(group);
}
fail:^(int code, NSString *desc) {
isNewLoadFail = YES;
failCode = code;
failDesc = desc;
dispatch_group_leave(group);
}];
}
@weakify(self);
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@strongify(self);
self.isLoadingData = NO;
if (isOldLoadFail || isNewLoadFail) {
dispatch_async(dispatch_get_main_queue(), ^{
failBlock(failCode, failDesc);
});
}
self.isFirstLoad = NO;
dispatch_async(dispatch_get_main_queue(), ^{
succeedBlock(olders, newers);
});
});
}
- (void)removeCache {
[self.medias removeAllObjects];
self.isNewerNoMoreMsg = NO;
self.isOlderNoMoreMsg = NO;
self.isFirstLoad = YES;
}
+ (TUIMessageCellData *)getMediaCellData:(V2TIMMessage *)message {
// subclass override required
return nil;
}
@end