增加换肤功能
This commit is contained in:
@@ -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
|
||||
258
TUIKit/TUIChat/BaseDataProvider/Base/TUIChatBaseDataProvider.m
Normal file
258
TUIKit/TUIChat/BaseDataProvider/Base/TUIChatBaseDataProvider.m
Normal 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
|
||||
@@ -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 page,supported 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
|
||||
@@ -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: reject、cancel、hangup、timeout、line_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: invite、cancel、hangup、timeout、line_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
|
||||
|
||||
// ********************************************************************
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
1602
TUIKit/TUIChat/BaseDataProvider/Base/TUIMessageBaseDataProvider.m
Normal file
1602
TUIKit/TUIChat/BaseDataProvider/Base/TUIMessageBaseDataProvider.m
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
36
TUIKit/TUIChat/BaseDataProvider/Impl/TUIChatDataProvider.h
Normal file
36
TUIKit/TUIChat/BaseDataProvider/Impl/TUIChatDataProvider.h
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TIMCommon/TIMInputViewMoreActionProtocol.h>
|
||||
#import "TUIChatBaseDataProvider.h"
|
||||
#import "TUIChatConversationModel.h"
|
||||
#import "TUIInputMoreCellData.h"
|
||||
#import "TUIVideoMessageCellData.h"
|
||||
|
||||
@class TUIChatDataProvider;
|
||||
@class TUICustomActionSheetItem;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIChatDataProvider : TUIChatBaseDataProvider
|
||||
|
||||
#pragma mark - CellData
|
||||
// For Classic Edition.
|
||||
- (NSMutableArray<TUIInputMoreCellData *> *)getMoreMenuCellDataArray:(NSString *)groupID
|
||||
userID:(NSString *)userID
|
||||
conversationModel:(TUIChatConversationModel *)conversationModel
|
||||
actionController:(id<TIMInputViewMoreActionProtocol>)actionController;
|
||||
|
||||
// For Minimalist Edition.
|
||||
- (NSArray<TUICustomActionSheetItem *> *)getInputMoreActionItemList:(nullable NSString *)userID
|
||||
groupID:(nullable NSString *)groupID
|
||||
conversationModel:(TUIChatConversationModel *)conversationModel
|
||||
pushVC:(nullable UINavigationController *)pushVC
|
||||
actionController:(id<TIMInputViewMoreActionProtocol>)actionController;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
503
TUIKit/TUIChat/BaseDataProvider/Impl/TUIChatDataProvider.m
Normal file
503
TUIKit/TUIChat/BaseDataProvider/Impl/TUIChatDataProvider.m
Normal file
@@ -0,0 +1,503 @@
|
||||
|
||||
// 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 "UIAlertController+TUICustomStyle.h"
|
||||
#import "TUIChatConfig.h"
|
||||
#import "TUIChatDataProvider.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
#import "TUIVideoMessageCellData.h"
|
||||
#import "TUIChatConversationModel.h"
|
||||
#import <TIMCommon/TIMCommonMediator.h>
|
||||
#import <TIMCommon/TUIEmojiMeditorProtocol.h>
|
||||
|
||||
#define Input_SendBtn_Key @"Input_SendBtn_Key"
|
||||
#define Input_SendBtn_Title @"Input_SendBtn_Title"
|
||||
#define Input_SendBtn_ImageName @"Input_SendBtn_ImageName"
|
||||
|
||||
@interface TUISplitEmojiData : NSObject
|
||||
@property (nonatomic, assign) NSInteger start;
|
||||
@property (nonatomic, assign) NSInteger end;
|
||||
@end
|
||||
|
||||
@implementation TUISplitEmojiData
|
||||
@end
|
||||
|
||||
@interface TUIChatDataProvider ()
|
||||
@property(nonatomic, strong) TUIInputMoreCellData *welcomeInputMoreMenu;
|
||||
@property(nonatomic, strong) NSMutableArray<TUIInputMoreCellData *> *customInputMoreMenus;
|
||||
@property(nonatomic, strong) NSArray<TUIInputMoreCellData *> *builtInInputMoreMenus;
|
||||
@property(nonatomic, strong) NSArray<TUICustomActionSheetItem *> *customInputMoreActionItemList;
|
||||
@property(nonatomic, strong) NSMutableArray<TUICustomActionSheetItem *> *builtInInputMoreActionItemList;
|
||||
@end
|
||||
|
||||
@implementation TUIChatDataProvider
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(onChangeLanguage) name:TUIChangeLanguageNotification object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
// For Classic Edition.
|
||||
- (NSMutableArray<TUIInputMoreCellData *> *)getMoreMenuCellDataArray:(NSString *)groupID
|
||||
userID:(NSString *)userID
|
||||
conversationModel:(TUIChatConversationModel *)conversationModel
|
||||
actionController:(id<TIMInputViewMoreActionProtocol>)actionController {
|
||||
self.builtInInputMoreMenus = [self createBuiltInInputMoreMenusWithConversationModel:conversationModel];
|
||||
NSMutableArray *moreMenus = [NSMutableArray array];
|
||||
[moreMenus addObjectsFromArray:self.builtInInputMoreMenus];
|
||||
|
||||
BOOL isNeedWelcomeCustomMessage = [TUIChatConfig defaultConfig].enableWelcomeCustomMessage && conversationModel.enableWelcomeCustomMessage;
|
||||
if (isNeedWelcomeCustomMessage) {
|
||||
if (![self.customInputMoreMenus containsObject:self.welcomeInputMoreMenu]) {
|
||||
[self.customInputMoreMenus addObject:self.welcomeInputMoreMenu];
|
||||
}
|
||||
}
|
||||
[moreMenus addObjectsFromArray:self.customInputMoreMenus];
|
||||
|
||||
// Extension items
|
||||
BOOL isNeedVideoCall = [TUIChatConfig defaultConfig].enableVideoCall && conversationModel.enableVideoCall;
|
||||
BOOL isNeedAudioCall = [TUIChatConfig defaultConfig].enableAudioCall && conversationModel.enableAudioCall;
|
||||
BOOL isNeedRoom = [TUIChatConfig defaultConfig].showRoomButton && conversationModel.enableRoom;
|
||||
BOOL isNeedPoll = [TUIChatConfig defaultConfig].showPollButton && conversationModel.enablePoll;
|
||||
BOOL isNeedGroupNote = [TUIChatConfig defaultConfig].showGroupNoteButton && conversationModel.enableGroupNote;
|
||||
|
||||
NSMutableDictionary *extensionParam = [NSMutableDictionary dictionary];
|
||||
if (userID.length > 0) {
|
||||
extensionParam[TUICore_TUIChatExtension_InputViewMoreItem_UserID] = userID;
|
||||
} else if (groupID.length > 0) {
|
||||
extensionParam[TUICore_TUIChatExtension_InputViewMoreItem_GroupID] = groupID;
|
||||
}
|
||||
extensionParam[TUICore_TUIChatExtension_InputViewMoreItem_FilterVideoCall] = @(!isNeedVideoCall);
|
||||
extensionParam[TUICore_TUIChatExtension_InputViewMoreItem_FilterAudioCall] = @(!isNeedAudioCall);
|
||||
extensionParam[TUICore_TUIChatExtension_InputViewMoreItem_FilterRoom] = @(!isNeedRoom);
|
||||
extensionParam[TUICore_TUIChatExtension_InputViewMoreItem_FilterPoll] = @(!isNeedPoll);
|
||||
extensionParam[TUICore_TUIChatExtension_InputViewMoreItem_FilterGroupNote] = @(!isNeedGroupNote);
|
||||
extensionParam[TUICore_TUIChatExtension_InputViewMoreItem_ActionVC] = actionController;
|
||||
NSArray *extensionList = [TUICore getExtensionList:TUICore_TUIChatExtension_InputViewMoreItem_ClassicExtensionID param:extensionParam];
|
||||
for (TUIExtensionInfo *info in extensionList) {
|
||||
NSAssert(info.icon && info.text && info.onClicked, @"extension for input view is invalid, check icon/text/onclick");
|
||||
if (info.icon && info.text && info.onClicked) {
|
||||
TUIInputMoreCellData *data = [[TUIInputMoreCellData alloc] init];
|
||||
data.priority = info.weight;
|
||||
data.image = info.icon;
|
||||
data.title = info.text;
|
||||
data.onClicked = info.onClicked;
|
||||
[moreMenus addObject:data];
|
||||
}
|
||||
}
|
||||
|
||||
// Customized items
|
||||
if (conversationModel.customizedNewItemsInMoreMenu.count > 0) {
|
||||
[moreMenus addObjectsFromArray:conversationModel.customizedNewItemsInMoreMenu];
|
||||
}
|
||||
|
||||
// Sort with priority
|
||||
NSArray *sortedMenus = [moreMenus sortedArrayUsingComparator:^NSComparisonResult(TUIInputMoreCellData *obj1, TUIInputMoreCellData *obj2) {
|
||||
return obj1.priority > obj2.priority ? NSOrderedAscending : NSOrderedDescending;
|
||||
}];
|
||||
return [NSMutableArray arrayWithArray:sortedMenus];
|
||||
}
|
||||
|
||||
// For Minimalist Edition.
|
||||
- (NSArray<TUICustomActionSheetItem *> *)getInputMoreActionItemList:(NSString *)userID
|
||||
groupID:(NSString *)groupID
|
||||
conversationModel:(TUIChatConversationModel *)conversationModel
|
||||
pushVC:(UINavigationController *)pushVC
|
||||
actionController:(id<TIMInputViewMoreActionProtocol>)actionController {
|
||||
NSMutableArray *result = [NSMutableArray array];
|
||||
[result addObjectsFromArray:[self createBuiltInInputMoreActionItemList:conversationModel]];
|
||||
[result addObjectsFromArray:[self createCustomInputMoreActionItemList:conversationModel]];
|
||||
|
||||
// Extension items
|
||||
NSMutableArray<TUICustomActionSheetItem *> *items = [NSMutableArray array];
|
||||
NSMutableDictionary *param = [NSMutableDictionary dictionary];
|
||||
if (userID.length > 0) {
|
||||
param[TUICore_TUIChatExtension_InputViewMoreItem_UserID] = userID;
|
||||
} else if (groupID.length > 0) {
|
||||
param[TUICore_TUIChatExtension_InputViewMoreItem_GroupID] = groupID;
|
||||
}
|
||||
param[TUICore_TUIChatExtension_InputViewMoreItem_FilterVideoCall] = @(!TUIChatConfig.defaultConfig.enableVideoCall);
|
||||
param[TUICore_TUIChatExtension_InputViewMoreItem_FilterAudioCall] = @(!TUIChatConfig.defaultConfig.enableAudioCall);
|
||||
if (pushVC) {
|
||||
param[TUICore_TUIChatExtension_InputViewMoreItem_PushVC] = pushVC;
|
||||
}
|
||||
param[TUICore_TUIChatExtension_InputViewMoreItem_ActionVC] = actionController;
|
||||
NSArray *extensionList = [TUICore getExtensionList:TUICore_TUIChatExtension_InputViewMoreItem_MinimalistExtensionID param:param];
|
||||
for (TUIExtensionInfo *info in extensionList) {
|
||||
if (info.icon && info.text && info.onClicked) {
|
||||
TUICustomActionSheetItem *item = [[TUICustomActionSheetItem alloc] initWithTitle:info.text
|
||||
leftMark:info.icon
|
||||
withActionHandler:^(UIAlertAction *_Nonnull action) {
|
||||
info.onClicked(param);
|
||||
}];
|
||||
item.priority = info.weight;
|
||||
[items addObject:item];
|
||||
}
|
||||
}
|
||||
if (items.count > 0) {
|
||||
[result addObjectsFromArray:items];
|
||||
}
|
||||
|
||||
// Sort with priority
|
||||
NSArray *sorted = [result sortedArrayUsingComparator:^NSComparisonResult(TUICustomActionSheetItem *obj1, TUICustomActionSheetItem *obj2) {
|
||||
return obj1.priority > obj2.priority ? NSOrderedAscending : NSOrderedDescending;
|
||||
}];
|
||||
return sorted;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
- (void)onChangeLanguage {
|
||||
self.customInputMoreActionItemList = nil;
|
||||
self.builtInInputMoreActionItemList = nil;
|
||||
}
|
||||
|
||||
#pragma mark -- Classic
|
||||
- (NSArray<TUIInputMoreCellData *> *)createBuiltInInputMoreMenusWithConversationModel:(TUIChatConversationModel *)conversationModel {
|
||||
|
||||
BOOL isNeedRecordVideo = [TUIChatConfig defaultConfig].showRecordVideoButton && conversationModel.enableRecordVideo;
|
||||
BOOL isNeedTakePhoto = [TUIChatConfig defaultConfig].showTakePhotoButton && conversationModel.enableTakePhoto;
|
||||
BOOL isNeedAlbum = [TUIChatConfig defaultConfig].showAlbumButton && conversationModel.enableAlbum;
|
||||
BOOL isNeedFile = [TUIChatConfig defaultConfig].showFileButton && conversationModel.enableFile;
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
TUIInputMoreCellData *albumData = [[TUIInputMoreCellData alloc] init];
|
||||
albumData.priority = 1000;
|
||||
albumData.title = TIMCommonLocalizableString(TUIKitMorePhoto);
|
||||
albumData.image = TUIChatBundleThemeImage(@"chat_more_picture_img", @"more_picture");
|
||||
albumData.onClicked = ^(NSDictionary *actionParam) {
|
||||
if ([weakSelf.delegate respondsToSelector:@selector(onSelectPhotoMoreCellData)]) {
|
||||
[weakSelf.delegate onSelectPhotoMoreCellData];
|
||||
}
|
||||
};
|
||||
|
||||
TUIInputMoreCellData *takePictureData = [[TUIInputMoreCellData alloc] init];
|
||||
takePictureData.priority = 900;
|
||||
takePictureData.title = TIMCommonLocalizableString(TUIKitMoreCamera);
|
||||
takePictureData.image = TUIChatBundleThemeImage(@"chat_more_camera_img", @"more_camera");
|
||||
takePictureData.onClicked = ^(NSDictionary *actionParam) {
|
||||
if ([weakSelf.delegate respondsToSelector:@selector(onTakePictureMoreCellData)]) {
|
||||
[weakSelf.delegate onTakePictureMoreCellData];
|
||||
}
|
||||
};
|
||||
|
||||
TUIInputMoreCellData *videoData = [[TUIInputMoreCellData alloc] init];
|
||||
videoData.priority = 800;
|
||||
videoData.title = TIMCommonLocalizableString(TUIKitMoreVideo);
|
||||
videoData.image = TUIChatBundleThemeImage(@"chat_more_video_img", @"more_video");
|
||||
videoData.onClicked = ^(NSDictionary *actionParam) {
|
||||
if ([weakSelf.delegate respondsToSelector:@selector(onTakeVideoMoreCellData)]) {
|
||||
[weakSelf.delegate onTakeVideoMoreCellData];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TUIInputMoreCellData *fileData = [[TUIInputMoreCellData alloc] init];
|
||||
fileData.priority = 700;
|
||||
fileData.title = TIMCommonLocalizableString(TUIKitMoreFile);
|
||||
fileData.image = TUIChatBundleThemeImage(@"chat_more_file_img", @"more_file");
|
||||
fileData.onClicked = ^(NSDictionary *actionParam) {
|
||||
if ([weakSelf.delegate respondsToSelector:@selector(onSelectFileMoreCellData)]) {
|
||||
[weakSelf.delegate onSelectFileMoreCellData];
|
||||
}
|
||||
};
|
||||
|
||||
NSMutableArray *formatArray = [NSMutableArray array];
|
||||
if (isNeedAlbum) {
|
||||
[formatArray addObject:albumData];
|
||||
}
|
||||
if (isNeedTakePhoto) {
|
||||
[formatArray addObject:takePictureData];
|
||||
}
|
||||
if (isNeedRecordVideo) {
|
||||
[formatArray addObject:videoData];
|
||||
}
|
||||
|
||||
if (isNeedFile) {
|
||||
[formatArray addObject:fileData];
|
||||
}
|
||||
_builtInInputMoreMenus = [NSArray arrayWithArray:formatArray];
|
||||
|
||||
return _builtInInputMoreMenus;
|
||||
}
|
||||
|
||||
#pragma mark -- Minimalist
|
||||
- (NSArray<TUICustomActionSheetItem *> *)createBuiltInInputMoreActionItemList:(TUIChatConversationModel *)model {
|
||||
if (_builtInInputMoreActionItemList == nil) {
|
||||
self.builtInInputMoreActionItemList = [NSMutableArray array];
|
||||
|
||||
BOOL showTakePhoto = [TUIChatConfig defaultConfig].showTakePhotoButton && model.enableTakePhoto;
|
||||
BOOL showAlbum = [TUIChatConfig defaultConfig].showAlbumButton && model.enableAlbum;
|
||||
BOOL showRecordVideo = [TUIChatConfig defaultConfig].showRecordVideoButton && model.enableRecordVideo;
|
||||
BOOL showFile = [TUIChatConfig defaultConfig].showFileButton && model.enableFile;
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
if (showAlbum) {
|
||||
TUICustomActionSheetItem *album =
|
||||
[[TUICustomActionSheetItem alloc] initWithTitle:TIMCommonLocalizableString(TUIKitMorePhoto)
|
||||
leftMark:[UIImage imageNamed:TUIChatImagePath_Minimalist(@"icon_more_photo")]
|
||||
withActionHandler:^(UIAlertAction *_Nonnull action) {
|
||||
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(onSelectPhotoMoreCellData)]) {
|
||||
[weakSelf.delegate onSelectPhotoMoreCellData];
|
||||
}
|
||||
}];
|
||||
album.priority = 1000;
|
||||
[self.builtInInputMoreActionItemList addObject:album];
|
||||
}
|
||||
|
||||
if (showTakePhoto) {
|
||||
TUICustomActionSheetItem *takePhoto =
|
||||
[[TUICustomActionSheetItem alloc] initWithTitle:TIMCommonLocalizableString(TUIKitMoreCamera)
|
||||
leftMark:[UIImage imageNamed:TUIChatImagePath_Minimalist(@"icon_more_camera")]
|
||||
withActionHandler:^(UIAlertAction *_Nonnull action) {
|
||||
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(onTakePictureMoreCellData)]) {
|
||||
[weakSelf.delegate onTakePictureMoreCellData];
|
||||
}
|
||||
}];
|
||||
takePhoto.priority = 900;
|
||||
[self.builtInInputMoreActionItemList addObject:takePhoto];
|
||||
}
|
||||
|
||||
if (showRecordVideo) {
|
||||
TUICustomActionSheetItem *recordVideo =
|
||||
[[TUICustomActionSheetItem alloc] initWithTitle:TIMCommonLocalizableString(TUIKitMoreVideo)
|
||||
leftMark:[UIImage imageNamed:TUIChatImagePath_Minimalist(@"icon_more_video")]
|
||||
withActionHandler:^(UIAlertAction *_Nonnull action) {
|
||||
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(onTakeVideoMoreCellData)]) {
|
||||
[weakSelf.delegate onTakeVideoMoreCellData];
|
||||
}
|
||||
}];
|
||||
recordVideo.priority = 800;
|
||||
[self.builtInInputMoreActionItemList addObject:recordVideo];
|
||||
}
|
||||
|
||||
if (showFile) {
|
||||
TUICustomActionSheetItem *file =
|
||||
[[TUICustomActionSheetItem alloc] initWithTitle:TIMCommonLocalizableString(TUIKitMoreFile)
|
||||
leftMark:[UIImage imageNamed:TUIChatImagePath_Minimalist(@"icon_more_document")]
|
||||
withActionHandler:^(UIAlertAction *_Nonnull action) {
|
||||
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(onSelectFileMoreCellData)]) {
|
||||
[weakSelf.delegate onSelectFileMoreCellData];
|
||||
}
|
||||
}];
|
||||
file.priority = 700;
|
||||
[self.builtInInputMoreActionItemList addObject:file];
|
||||
}
|
||||
}
|
||||
return self.builtInInputMoreActionItemList;
|
||||
}
|
||||
|
||||
- (NSArray<TUICustomActionSheetItem *> *)createCustomInputMoreActionItemList:(TUIChatConversationModel *)model {
|
||||
if (_customInputMoreActionItemList == nil) {
|
||||
NSMutableArray *arrayM = [NSMutableArray array];
|
||||
|
||||
BOOL showCustom = [TUIChatConfig defaultConfig].enableWelcomeCustomMessage && model.enableWelcomeCustomMessage;
|
||||
if (showCustom) {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
TUICustomActionSheetItem *link =
|
||||
[[TUICustomActionSheetItem alloc] initWithTitle:TIMCommonLocalizableString(TUIKitMoreLink)
|
||||
leftMark:[UIImage imageNamed:TUIChatImagePath_Minimalist(@"icon_more_custom")]
|
||||
withActionHandler:^(UIAlertAction *_Nonnull action) {
|
||||
link.priority = 100;
|
||||
NSString *text = TIMCommonLocalizableString(TUIKitWelcome);
|
||||
NSString *link = TUITencentCloudHomePageEN;
|
||||
NSString *language = [TUIGlobalization tk_localizableLanguageKey];
|
||||
if ([language tui_containsString:@"zh-"]) {
|
||||
link = TUITencentCloudHomePageCN;
|
||||
}
|
||||
NSError *error = nil;
|
||||
NSDictionary *param = @{BussinessID : BussinessID_TextLink, @"text" : text, @"link" : link};
|
||||
NSData *data = [NSJSONSerialization dataWithJSONObject:param options:0 error:&error];
|
||||
if (error) {
|
||||
NSLog(@"[%@] Post Json Error", [self class]);
|
||||
return;
|
||||
}
|
||||
V2TIMMessage *message = [TUIMessageDataProvider getCustomMessageWithJsonData:data desc:text extension:text];
|
||||
if ([weakSelf.delegate respondsToSelector:@selector(dataProvider:sendMessage:)]) {
|
||||
[weakSelf.delegate dataProvider:weakSelf sendMessage:message];
|
||||
}
|
||||
}];
|
||||
[arrayM addObject:link];
|
||||
}
|
||||
|
||||
if (model.customizedNewItemsInMoreMenu.count > 0) {
|
||||
[arrayM addObjectsFromArray:model.customizedNewItemsInMoreMenu];
|
||||
}
|
||||
_customInputMoreActionItemList = [NSArray arrayWithArray:arrayM];
|
||||
}
|
||||
return _customInputMoreActionItemList;
|
||||
}
|
||||
|
||||
#pragma mark -- Override
|
||||
- (NSString *)abstractDisplayWithMessage:(V2TIMMessage *)msg {
|
||||
NSString *desc = @"";
|
||||
if (msg.nickName.length > 0) {
|
||||
desc = msg.nickName;
|
||||
} else if (msg.sender.length > 0) {
|
||||
desc = msg.sender;
|
||||
}
|
||||
NSString *display = [self.delegate dataProvider:self mergeForwardMsgAbstactForMessage:msg];
|
||||
|
||||
if (display.length == 0) {
|
||||
display = [self.class parseAbstractDisplayWStringFromMessageElement:msg];
|
||||
}
|
||||
NSString * splitStr = @":";
|
||||
splitStr = @"\u202C:";
|
||||
|
||||
NSString *nameFormat = [desc stringByAppendingFormat:@"%@", splitStr];
|
||||
return [self.class alignEmojiStringWithUserName:nameFormat
|
||||
text:display];
|
||||
}
|
||||
|
||||
+ (nullable NSString *)parseAbstractDisplayWStringFromMessageElement:(V2TIMMessage *)message {
|
||||
NSString *str = nil;
|
||||
if (message.elemType == V2TIM_ELEM_TYPE_TEXT) {
|
||||
NSString *content = message.textElem.text;
|
||||
str = content;
|
||||
}
|
||||
else {
|
||||
str = [TUIMessageDataProvider getDisplayString:message];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
+ (NSString *)alignEmojiStringWithUserName:(NSString *)userName text:(NSString *)text {
|
||||
NSArray *textList = [self.class splitEmojiText:text];
|
||||
NSInteger forwardMsgLength = 98;
|
||||
NSMutableString *sb = [NSMutableString string];
|
||||
[sb appendString:userName];
|
||||
NSInteger length = userName.length;
|
||||
for (NSString *textItem in textList) {
|
||||
BOOL isFaceChar = [self.class isFaceStrKey:textItem];
|
||||
if (isFaceChar) {
|
||||
if (length + textItem.length < forwardMsgLength) {
|
||||
[sb appendString:textItem];
|
||||
length += textItem.length;
|
||||
} else {
|
||||
[sb appendString:@"..."];
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (length + textItem.length < forwardMsgLength) {
|
||||
[sb appendString:textItem];
|
||||
length += textItem.length;
|
||||
} else {
|
||||
[sb appendString:textItem];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
+ (BOOL)isFaceStrKey:(NSString*) strkey {
|
||||
id<TUIEmojiMeditorProtocol> service = [[TIMCommonMediator share] getObject:@protocol(TUIEmojiMeditorProtocol)];
|
||||
NSArray <TUIFaceGroup *> * groups = service.getFaceGroup;
|
||||
if ([groups.firstObject.facesMap objectForKey:strkey] != nil) {
|
||||
return YES;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSArray<NSString *> *)splitEmojiText:(NSString *)text {
|
||||
NSString *regex = @"\\[(\\S+?)\\]";
|
||||
NSRegularExpression *regexExp = [NSRegularExpression regularExpressionWithPattern:regex options:0 error:nil];
|
||||
NSArray<NSTextCheckingResult *> *matches = [regexExp matchesInString:text options:0 range:NSMakeRange(0, text.length)];
|
||||
NSMutableArray<TUISplitEmojiData *> *emojiDataList = [NSMutableArray array];
|
||||
NSInteger lastMentionIndex = -1;
|
||||
for (NSTextCheckingResult *match in matches) {
|
||||
NSString *emojiKey = [text substringWithRange:match.range];
|
||||
NSInteger start;
|
||||
if (lastMentionIndex != -1) {
|
||||
start = [text rangeOfString:emojiKey options:0 range:NSMakeRange(lastMentionIndex, text.length - lastMentionIndex)].location;
|
||||
} else {
|
||||
start = [text rangeOfString:emojiKey].location;
|
||||
}
|
||||
NSInteger end = start + emojiKey.length;
|
||||
lastMentionIndex = end;
|
||||
|
||||
|
||||
if (![self.class isFaceStrKey:emojiKey]) {
|
||||
continue;
|
||||
}
|
||||
TUISplitEmojiData *emojiData = [[TUISplitEmojiData alloc] init];
|
||||
emojiData.start = start;
|
||||
emojiData.end = end;
|
||||
[emojiDataList addObject:emojiData];
|
||||
}
|
||||
NSMutableArray<NSString *> *stringList = [NSMutableArray array];
|
||||
NSInteger offset = 0;
|
||||
for (TUISplitEmojiData *emojiData in emojiDataList) {
|
||||
NSInteger start = emojiData.start - offset;
|
||||
NSInteger end = emojiData.end - offset;
|
||||
NSString *startStr = [text substringToIndex:start];
|
||||
NSString *middleStr = [text substringWithRange:NSMakeRange(start, end - start)];
|
||||
text = [text substringFromIndex:end];
|
||||
if (startStr.length > 0) {
|
||||
[stringList addObject:startStr];
|
||||
}
|
||||
[stringList addObject:middleStr];
|
||||
offset += startStr.length + middleStr.length;
|
||||
}
|
||||
if (text.length > 0) {
|
||||
[stringList addObject:text];
|
||||
}
|
||||
return stringList;
|
||||
}
|
||||
|
||||
#pragma mark - Getter
|
||||
- (TUIInputMoreCellData *)welcomeInputMoreMenu {
|
||||
if (!_welcomeInputMoreMenu) {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
_welcomeInputMoreMenu = [[TUIInputMoreCellData alloc] init];
|
||||
_welcomeInputMoreMenu.priority = 0;
|
||||
_welcomeInputMoreMenu.title = TIMCommonLocalizableString(TUIKitMoreLink);
|
||||
_welcomeInputMoreMenu.image = TUIChatBundleThemeImage(@"chat_more_link_img", @"chat_more_link_img");
|
||||
_welcomeInputMoreMenu.onClicked = ^(NSDictionary *actionParam) {
|
||||
NSString *text = TIMCommonLocalizableString(TUIKitWelcome);
|
||||
NSString *link = TUITencentCloudHomePageEN;
|
||||
NSString *language = [TUIGlobalization tk_localizableLanguageKey];
|
||||
if ([language tui_containsString:@"zh-"]) {
|
||||
link = TUITencentCloudHomePageCN;
|
||||
}
|
||||
NSError *error = nil;
|
||||
NSDictionary *param = @{BussinessID : BussinessID_TextLink, @"text" : text, @"link" : link};
|
||||
NSData *data = [NSJSONSerialization dataWithJSONObject:param options:0 error:&error];
|
||||
if (error) {
|
||||
NSLog(@"[%@] Post Json Error", [weakSelf class]);
|
||||
return;
|
||||
}
|
||||
V2TIMMessage *message = [TUIMessageDataProvider getCustomMessageWithJsonData:data desc:text extension:text];
|
||||
if ([weakSelf.delegate respondsToSelector:@selector(dataProvider:sendMessage:)]) {
|
||||
[weakSelf.delegate dataProvider:weakSelf sendMessage:message];
|
||||
}
|
||||
};
|
||||
}
|
||||
return _welcomeInputMoreMenu;
|
||||
}
|
||||
|
||||
- (NSMutableArray<TUIInputMoreCellData *> *)customInputMoreMenus {
|
||||
if (!_customInputMoreMenus) {
|
||||
_customInputMoreMenus = [NSMutableArray array];
|
||||
}
|
||||
return _customInputMoreMenus;
|
||||
}
|
||||
|
||||
- (NSArray<TUIInputMoreCellData *> *)builtInInputMoreMenus {
|
||||
if (_builtInInputMoreMenus == nil) {
|
||||
return [self createBuiltInInputMoreMenusWithConversationModel:nil];
|
||||
}
|
||||
return _builtInInputMoreMenus;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// TUIGroupNoticeDataProvider.h
|
||||
// TUIGroup
|
||||
//
|
||||
// Created by harvy on 2022/1/12.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <ImSDK_Plus/ImSDK_Plus.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIGroupNoticeDataProvider : NSObject
|
||||
|
||||
@property(nonatomic, strong, readonly) V2TIMGroupInfo *groupInfo;
|
||||
@property(nonatomic, copy) NSString *groupID;
|
||||
|
||||
- (void)getGroupInfo:(dispatch_block_t)callback;
|
||||
- (BOOL)canEditNotice;
|
||||
- (void)updateNotice:(NSString *)notice callback:(void (^)(int, NSString *))callback;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// TUIGroupNoticeDataProvider.m
|
||||
// TUIGroup
|
||||
//
|
||||
// Created by harvy on 2022/1/12.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIGroupNoticeDataProvider.h"
|
||||
|
||||
@interface TUIGroupNoticeDataProvider ()
|
||||
|
||||
@property(nonatomic, strong) V2TIMGroupInfo *groupInfo;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIGroupNoticeDataProvider
|
||||
|
||||
- (void)getGroupInfo:(dispatch_block_t)callback {
|
||||
if (self.groupInfo && [self.groupInfo.groupID isEqual:self.groupID]) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[V2TIMManager.sharedInstance getGroupsInfo:@[ self.groupID ?: @"" ]
|
||||
succ:^(NSArray<V2TIMGroupInfoResult *> *groupResultList) {
|
||||
V2TIMGroupInfoResult *result = groupResultList.firstObject;
|
||||
if (result && result.resultCode == 0) {
|
||||
weakSelf.groupInfo = result.info;
|
||||
}
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)canEditNotice {
|
||||
return self.groupInfo.role == V2TIM_GROUP_MEMBER_ROLE_ADMIN || self.groupInfo.role == V2TIM_GROUP_MEMBER_ROLE_SUPER;
|
||||
}
|
||||
|
||||
- (void)updateNotice:(NSString *)notice callback:(void (^)(int, NSString *))callback {
|
||||
V2TIMGroupInfo *info = [[V2TIMGroupInfo alloc] init];
|
||||
info.groupID = self.groupID;
|
||||
info.notification = notice;
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[V2TIMManager.sharedInstance setGroupInfo:info
|
||||
succ:^{
|
||||
if (callback) {
|
||||
callback(0, nil);
|
||||
}
|
||||
|
||||
[weakSelf sendNoticeMessage:notice];
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
if (callback) {
|
||||
callback(code, desc);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)sendNoticeMessage:(NSString *)notice {
|
||||
if (notice.length == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// TUIMessageDataProvider+MessageDeal.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/3/22.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMessageDataProvider.h"
|
||||
#import "TUIReplyMessageCellData.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMessageDataProvider (MessageDeal)
|
||||
- (void)loadOriginMessageFromReplyData:(TUIReplyMessageCellData *)replycellData dealCallback:(void (^)(void))callback;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// TUIMessageDataProvider+MessageDeal.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/3/22.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TUIMessageCellData.h>
|
||||
#import "TUIChatDataProvider.h"
|
||||
#import "TUIImageMessageCellData.h"
|
||||
#import "TUIMessageDataProvider+MessageDeal.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
@implementation TUIMessageDataProvider (MessageDeal)
|
||||
|
||||
- (void)loadOriginMessageFromReplyData:(TUIReplyMessageCellData *)replycellData dealCallback:(void (^)(void))callback {
|
||||
if (replycellData.originMsgID.length == 0) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@weakify(replycellData)[TUIChatDataProvider findMessages:@[ replycellData.originMsgID ]
|
||||
callback:^(BOOL succ, NSString *_Nonnull error_message, NSArray *_Nonnull msgs) {
|
||||
@strongify(replycellData) if (!succ) {
|
||||
replycellData.quoteData = [replycellData getQuoteData:nil];
|
||||
replycellData.originMessage = nil;
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
V2TIMMessage *originMessage = msgs.firstObject;
|
||||
if (originMessage == nil) {
|
||||
replycellData.quoteData = [replycellData getQuoteData:nil];
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
TUIMessageCellData *cellData = [TUIMessageDataProvider getCellData:originMessage];
|
||||
replycellData.originCellData = cellData;
|
||||
if ([cellData isKindOfClass:TUIImageMessageCellData.class]) {
|
||||
TUIImageMessageCellData *imageData = (TUIImageMessageCellData *)cellData;
|
||||
[imageData downloadImage:TImage_Type_Thumb];
|
||||
replycellData.quoteData = [replycellData getQuoteData:imageData];
|
||||
replycellData.originMessage = originMessage;
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
} else if ([cellData isKindOfClass:TUIVideoMessageCellData.class]) {
|
||||
TUIVideoMessageCellData *videoData = (TUIVideoMessageCellData *)cellData;
|
||||
[videoData downloadThumb];
|
||||
replycellData.quoteData = [replycellData getQuoteData:videoData];
|
||||
replycellData.originMessage = originMessage;
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
replycellData.quoteData = [replycellData getQuoteData:cellData];
|
||||
replycellData.originMessage = originMessage;
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,46 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "TUIMessageBaseDataProvider.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUITextMessageCellData;
|
||||
@class TUIFaceMessageCellData;
|
||||
@class TUIImageMessageCellData;
|
||||
@class TUIVoiceMessageCellData;
|
||||
@class TUIVideoMessageCellData;
|
||||
@class TUIFileMessageCellData;
|
||||
@class TUISystemMessageCellData;
|
||||
@class TUIChatCallingDataProvider;
|
||||
|
||||
@protocol TUIMessageDataProviderDataSource <TUIMessageBaseDataProviderDataSource>
|
||||
|
||||
+ (nullable Class)onGetCustomMessageCellDataClass:(NSString *)businessID;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIMessageDataProvider : TUIMessageBaseDataProvider
|
||||
|
||||
+ (void)setDataSourceClass:(Class<TUIMessageDataProviderDataSource>)dataSourceClass;
|
||||
|
||||
#pragma mark - TUIMessageCellData parser
|
||||
+ (nullable TUIMessageCellData *)getCellData:(V2TIMMessage *)message;
|
||||
|
||||
#pragma mark - Last message parser
|
||||
+ (void)asyncGetDisplayString:(NSArray<V2TIMMessage *> *)messageList callback:(void(^)(NSDictionary<NSString *, NSString *> *))callback;
|
||||
+ (nullable NSString *)getDisplayString:(V2TIMMessage *)message;
|
||||
|
||||
#pragma mark - Data source operate
|
||||
- (void)processQuoteMessage:(NSArray<TUIMessageCellData *> *)uiMsgs;
|
||||
- (void)deleteUIMsgs:(NSArray<TUIMessageCellData *> *)uiMsgs SuccBlock:(nullable V2TIMSucc)succ FailBlock:(nullable V2TIMFail)fail;
|
||||
- (void)removeUIMsgList:(NSArray<TUIMessageCellData *> *)cellDatas;
|
||||
|
||||
|
||||
+ (TUIChatCallingDataProvider *)callingDataProvider;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
754
TUIKit/TUIChat/BaseDataProvider/Impl/TUIMessageDataProvider.m
Normal file
754
TUIKit/TUIChat/BaseDataProvider/Impl/TUIMessageDataProvider.m
Normal file
@@ -0,0 +1,754 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import "TUIMessageDataProvider.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <TIMCommon/TUIRelationUserModel.h>
|
||||
#import <TIMCommon/TUISystemMessageCellData.h>
|
||||
#import <TUICore/TUILogin.h>
|
||||
#import "TUIChatCallingDataProvider.h"
|
||||
#import "TUICloudCustomDataTypeCenter.h"
|
||||
#import "TUIFaceMessageCellData.h"
|
||||
#import "TUIFileMessageCellData.h"
|
||||
#import "TUIImageMessageCellData.h"
|
||||
#import "TUIJoinGroupMessageCellData.h"
|
||||
#import "TUIMergeMessageCellData.h"
|
||||
#import "TUIMessageDataProvider+MessageDeal.h"
|
||||
#import "TUIMessageProgressManager.h"
|
||||
#import "TUIReplyMessageCellData.h"
|
||||
#import "TUITextMessageCellData.h"
|
||||
#import "TUIVideoMessageCellData.h"
|
||||
#import "TUIVoiceMessageCellData.h"
|
||||
|
||||
/**
|
||||
* The maximum editable time after the message is recalled, default is (2 * 60)
|
||||
*/
|
||||
#define MaxReEditMessageDelay 2 * 60
|
||||
|
||||
#define kIsCustomMessageFromPlugin @"kIsCustomMessageFromPlugin"
|
||||
|
||||
static Class<TUIMessageDataProviderDataSource> gDataSourceClass = nil;
|
||||
|
||||
@implementation TUIMessageDataProvider
|
||||
|
||||
#pragma mark - Life cycle
|
||||
|
||||
- (void)dealloc {
|
||||
gCallingDataProvider = nil;
|
||||
}
|
||||
|
||||
+ (void)setDataSourceClass:(Class<TUIMessageDataProviderDataSource>)dataSourceClass {
|
||||
gDataSourceClass = dataSourceClass;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageCellData parser
|
||||
+ (nullable TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
|
||||
// 1 Parse cell data
|
||||
TUIMessageCellData *data = [self parseMessageCellDataFromMessageStatus:message];
|
||||
if (data == nil) {
|
||||
data = [self parseMessageCellDataFromMessageCustomData:message];
|
||||
}
|
||||
if (data == nil) {
|
||||
data = [self parseMessageCellDataFromMessageElement:message];
|
||||
}
|
||||
|
||||
// 2 Fill in property if needed
|
||||
if (data) {
|
||||
[self fillPropertyToCellData:data ofMessage:message];
|
||||
} else {
|
||||
NSLog(@"current message will be ignored in chat page, msg:%@", message);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
+ (nullable TUIMessageCellData *)parseMessageCellDataFromMessageStatus:(V2TIMMessage *)message {
|
||||
TUIMessageCellData *data = nil;
|
||||
if (message.status == V2TIM_MSG_STATUS_LOCAL_REVOKED) {
|
||||
data = [TUIMessageDataProvider getRevokeCellData:message];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
+ (nullable TUIMessageCellData *)parseMessageCellDataFromMessageCustomData:(V2TIMMessage *)message {
|
||||
TUIMessageCellData *data = nil;
|
||||
if ([message isContainsCloudCustomOfDataType:TUICloudCustomDataType_MessageReply]) {
|
||||
/**
|
||||
* Determine whether to include "reply-message"
|
||||
*/
|
||||
data = [TUIReplyMessageCellData getCellData:message];
|
||||
} else if ([message isContainsCloudCustomOfDataType:TUICloudCustomDataType_MessageReference]) {
|
||||
/**
|
||||
* Determine whether to include "quote-message"
|
||||
*/
|
||||
data = [TUIReferenceMessageCellData getCellData:message];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
+ (nullable TUIMessageCellData *)parseMessageCellDataFromMessageElement:(V2TIMMessage *)message {
|
||||
TUIMessageCellData *data = nil;
|
||||
switch (message.elemType) {
|
||||
case V2TIM_ELEM_TYPE_TEXT: {
|
||||
data = [TUITextMessageCellData getCellData:message];
|
||||
} break;
|
||||
case V2TIM_ELEM_TYPE_IMAGE: {
|
||||
data = [TUIImageMessageCellData getCellData:message];
|
||||
} break;
|
||||
case V2TIM_ELEM_TYPE_SOUND: {
|
||||
data = [TUIVoiceMessageCellData getCellData:message];
|
||||
} break;
|
||||
case V2TIM_ELEM_TYPE_VIDEO: {
|
||||
data = [TUIVideoMessageCellData getCellData:message];
|
||||
} break;
|
||||
case V2TIM_ELEM_TYPE_FILE: {
|
||||
data = [TUIFileMessageCellData getCellData:message];
|
||||
} break;
|
||||
case V2TIM_ELEM_TYPE_FACE: {
|
||||
data = [TUIFaceMessageCellData getCellData:message];
|
||||
} break;
|
||||
case V2TIM_ELEM_TYPE_GROUP_TIPS: {
|
||||
data = [self getSystemCellData:message];
|
||||
} break;
|
||||
case V2TIM_ELEM_TYPE_MERGER: {
|
||||
data = [TUIMergeMessageCellData getCellData:message];
|
||||
} break;
|
||||
case V2TIM_ELEM_TYPE_CUSTOM: {
|
||||
data = [self getCustomMessageCellData:message];
|
||||
} break;
|
||||
default:
|
||||
data = [self getUnsupportedCellData:message];
|
||||
break;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
+ (void)fillPropertyToCellData:(TUIMessageCellData *)data ofMessage:(V2TIMMessage *)message {
|
||||
data.innerMessage = message;
|
||||
if (message.groupID.length > 0 && !message.isSelf && ![data isKindOfClass:[TUISystemMessageCellData class]]) {
|
||||
data.showName = YES;
|
||||
}
|
||||
switch (message.status) {
|
||||
case V2TIM_MSG_STATUS_SEND_SUCC:
|
||||
data.status = Msg_Status_Succ;
|
||||
break;
|
||||
case V2TIM_MSG_STATUS_SEND_FAIL:
|
||||
data.status = Msg_Status_Fail;
|
||||
break;
|
||||
case V2TIM_MSG_STATUS_SENDING:
|
||||
data.status = Msg_Status_Sending_2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update progress of message uploading/downloading
|
||||
*/
|
||||
{
|
||||
NSInteger uploadProgress = [TUIMessageProgressManager.shareManager uploadProgressForMessage:message.msgID];
|
||||
NSInteger downloadProgress = [TUIMessageProgressManager.shareManager downloadProgressForMessage:message.msgID];
|
||||
if ([data conformsToProtocol:@protocol(TUIMessageCellDataFileUploadProtocol)]) {
|
||||
((id<TUIMessageCellDataFileUploadProtocol>)data).uploadProgress = uploadProgress;
|
||||
}
|
||||
if ([data conformsToProtocol:@protocol(TUIMessageCellDataFileDownloadProtocol)]) {
|
||||
((id<TUIMessageCellDataFileDownloadProtocol>)data).downladProgress = downloadProgress;
|
||||
((id<TUIMessageCellDataFileDownloadProtocol>)data).isDownloading = (downloadProgress != 0) && (downloadProgress != 100);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether to include "replies-message"
|
||||
*/
|
||||
if ([message isContainsCloudCustomOfDataType:TUICloudCustomDataType_MessageReplies]) {
|
||||
[message doThingsInContainsCloudCustomOfDataType:TUICloudCustomDataType_MessageReplies
|
||||
callback:^(BOOL isContains, id obj) {
|
||||
if (isContains) {
|
||||
if ([data isKindOfClass:TUISystemMessageCellData.class] ||
|
||||
[data isKindOfClass:TUIJoinGroupMessageCellData.class]) {
|
||||
data.showMessageModifyReplies = NO;
|
||||
} else {
|
||||
data.showMessageModifyReplies = YES;
|
||||
}
|
||||
if (obj && [obj isKindOfClass:NSDictionary.class]) {
|
||||
NSDictionary *dic = (NSDictionary *)obj;
|
||||
NSString *typeStr =
|
||||
[TUICloudCustomDataTypeCenter convertType2String:TUICloudCustomDataType_MessageReplies];
|
||||
NSDictionary *messageReplies = [dic valueForKey:typeStr];
|
||||
NSArray *repliesArr = [messageReplies valueForKey:@"replies"];
|
||||
if ([repliesArr isKindOfClass:NSArray.class]) {
|
||||
data.messageModifyReplies = repliesArr.copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
+ (nullable TUIMessageCellData *)getCustomMessageCellData:(V2TIMMessage *)message {
|
||||
// ************************************************************************************
|
||||
// ************************************************************************************
|
||||
// **The compatible processing logic of TUICallKit will be removed after***************
|
||||
// **TUICallKit is connected according to the standard process. ***********************
|
||||
// ************************************************************************************
|
||||
// ************************************************************************************
|
||||
TUIMessageCellData *data = nil;
|
||||
id<TUIChatCallingInfoProtocol> callingInfo = nil;
|
||||
if ([self.callingDataProvider isCallingMessage:message callingInfo:&callingInfo]) {
|
||||
// Voice-video-call message
|
||||
if (callingInfo) {
|
||||
// Supported voice-video-call message
|
||||
if (callingInfo.excludeFromHistory) {
|
||||
// This message will be ignore in chat page
|
||||
data = nil;
|
||||
} else {
|
||||
data = [self getCallingCellData:callingInfo];
|
||||
if (data == nil) {
|
||||
data = [self getUnsupportedCellData:message];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Unsupported voice-video-call message
|
||||
data = [self getUnsupportedCellData:message];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
// ************************************************************************************
|
||||
// ************************************************************************************
|
||||
// ************************************************************************************
|
||||
// ************************************************************************************
|
||||
|
||||
NSString *businessID = nil;
|
||||
BOOL excludeFromHistory = NO;
|
||||
|
||||
V2TIMSignalingInfo *signalingInfo = [V2TIMManager.sharedInstance getSignallingInfo:message];
|
||||
if (signalingInfo) {
|
||||
// This message is signaling message
|
||||
BOOL isOnlineOnly = NO;
|
||||
@try {
|
||||
isOnlineOnly = [[message valueForKeyPath:@"message.IsOnlineOnly"] boolValue];
|
||||
} @catch (NSException *exception) {
|
||||
isOnlineOnly = NO;
|
||||
}
|
||||
excludeFromHistory = isOnlineOnly || (message.isExcludedFromLastMessage && message.isExcludedFromUnreadCount);
|
||||
businessID = [self getSignalingBusinessID:signalingInfo];
|
||||
} else {
|
||||
// This message is normal custom message
|
||||
excludeFromHistory = NO;
|
||||
businessID = [self getCustomBusinessID:message];
|
||||
}
|
||||
|
||||
if (excludeFromHistory) {
|
||||
// Return nil means not display in the chat page
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (businessID.length > 0) {
|
||||
Class cellDataClass = nil;
|
||||
if (gDataSourceClass && [gDataSourceClass respondsToSelector:@selector(onGetCustomMessageCellDataClass:)]) {
|
||||
cellDataClass = [gDataSourceClass onGetCustomMessageCellDataClass:businessID];
|
||||
}
|
||||
if (cellDataClass && [cellDataClass respondsToSelector:@selector(getCellData:)]) {
|
||||
TUIMessageCellData *data = [cellDataClass getCellData:message];
|
||||
if (data.shouldHide) {
|
||||
return nil;
|
||||
} else {
|
||||
data.reuseId = businessID;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
// In CustomerService scenarios, unsupported messages are not displayed directly.
|
||||
if ([businessID tui_containsString:BussinessID_CustomerService]) {
|
||||
return nil;
|
||||
}
|
||||
return [self getUnsupportedCellData:message];
|
||||
} else {
|
||||
return [self getUnsupportedCellData:message];
|
||||
}
|
||||
}
|
||||
|
||||
+ (TUIMessageCellData *)getUnsupportedCellData:(V2TIMMessage *)message {
|
||||
TUITextMessageCellData *cellData = [[TUITextMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
|
||||
cellData.content = TIMCommonLocalizableString(TUIKitNotSupportThisMessage);
|
||||
cellData.reuseId = TTextMessageCell_ReuseId;
|
||||
return cellData;
|
||||
}
|
||||
|
||||
+ (nullable TUISystemMessageCellData *)getSystemCellData:(V2TIMMessage *)message {
|
||||
V2TIMGroupTipsElem *tip = message.groupTipsElem;
|
||||
NSString *opUserName = [self getOpUserName:tip.opMember];
|
||||
NSMutableArray<NSString *> *userNameList = [self getUserNameList:tip.memberList];
|
||||
NSMutableArray<NSString *> *userIDList = [self getUserIDList:tip.memberList];
|
||||
if (tip.type == V2TIM_GROUP_TIPS_TYPE_JOIN || tip.type == V2TIM_GROUP_TIPS_TYPE_INVITE || tip.type == V2TIM_GROUP_TIPS_TYPE_KICKED ||
|
||||
tip.type == V2TIM_GROUP_TIPS_TYPE_GROUP_INFO_CHANGE || tip.type == V2TIM_GROUP_TIPS_TYPE_QUIT ||
|
||||
tip.type == V2TIM_GROUP_TIPS_TYPE_PINNED_MESSAGE_ADDED || tip.type == V2TIM_GROUP_TIPS_TYPE_PINNED_MESSAGE_DELETED) {
|
||||
TUIJoinGroupMessageCellData *joinGroupData = [[TUIJoinGroupMessageCellData alloc] initWithDirection:MsgDirectionIncoming];
|
||||
joinGroupData.content = [self getDisplayString:message];
|
||||
joinGroupData.opUserName = opUserName;
|
||||
joinGroupData.opUserID = tip.opMember.userID;
|
||||
joinGroupData.userNameList = userNameList;
|
||||
joinGroupData.userIDList = userIDList;
|
||||
joinGroupData.reuseId = TJoinGroupMessageCell_ReuseId;
|
||||
return joinGroupData;
|
||||
} else {
|
||||
TUISystemMessageCellData *sysdata = [[TUISystemMessageCellData alloc] initWithDirection:MsgDirectionIncoming];
|
||||
sysdata.content = [self getDisplayString:message];
|
||||
sysdata.reuseId = TSystemMessageCell_ReuseId;
|
||||
if (sysdata.content.length) {
|
||||
return sysdata;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (nullable TUISystemMessageCellData *)getRevokeCellData:(V2TIMMessage *)message {
|
||||
TUISystemMessageCellData *revoke = [[TUISystemMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
|
||||
revoke.reuseId = TSystemMessageCell_ReuseId;
|
||||
revoke.content = [self getRevokeDispayString:message];
|
||||
revoke.innerMessage = message;
|
||||
V2TIMUserFullInfo *revokerInfo = message.revokerInfo;
|
||||
if (message.isSelf) {
|
||||
if (message.elemType == V2TIM_ELEM_TYPE_TEXT && fabs([[NSDate date] timeIntervalSinceDate:message.timestamp]) < MaxReEditMessageDelay) {
|
||||
if (revokerInfo && ![revokerInfo.userID isEqualToString:message.sender]) {
|
||||
// Super User revoke
|
||||
revoke.supportReEdit = NO;
|
||||
} else {
|
||||
revoke.supportReEdit = YES;
|
||||
}
|
||||
}
|
||||
} else if (message.groupID.length > 0) {
|
||||
/**
|
||||
* For the name display of group messages, the group business card is displayed first, the nickname has the second priority, and the user ID has the
|
||||
* lowest priority.
|
||||
*/
|
||||
NSString *userName = [TUIMessageDataProvider getShowName:message];
|
||||
TUIJoinGroupMessageCellData *joinGroupData = [[TUIJoinGroupMessageCellData alloc] initWithDirection:MsgDirectionIncoming];
|
||||
joinGroupData.content = [self getRevokeDispayString:message];
|
||||
joinGroupData.opUserID = message.sender;
|
||||
joinGroupData.opUserName = userName;
|
||||
joinGroupData.reuseId = TJoinGroupMessageCell_ReuseId;
|
||||
return joinGroupData;
|
||||
}
|
||||
return revoke;
|
||||
}
|
||||
|
||||
+ (nullable TUISystemMessageCellData *)getSystemMsgFromDate:(NSDate *)date {
|
||||
TUISystemMessageCellData *system = [[TUISystemMessageCellData alloc] initWithDirection:MsgDirectionOutgoing];
|
||||
system.content = [TUITool convertDateToStr:date];
|
||||
system.reuseId = TSystemMessageCell_ReuseId;
|
||||
system.type = TUISystemMessageTypeDate;
|
||||
return system;
|
||||
}
|
||||
|
||||
#pragma mark - Last message parser
|
||||
+ (void)asyncGetDisplayString:(NSArray<V2TIMMessage *> *)messageList callback:(void (^)(NSDictionary<NSString *, NSString *> *))callback {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary *originDisplayMap = [NSMutableDictionary dictionary];
|
||||
NSMutableArray *cellDataList = [NSMutableArray array];
|
||||
for (V2TIMMessage *message in messageList) {
|
||||
TUIMessageCellData *cellData = [self getCellData:message];
|
||||
if (cellData) {
|
||||
[cellDataList addObject:cellData];
|
||||
}
|
||||
|
||||
NSString *displayString = [self getDisplayString:message];
|
||||
if (displayString && message.msgID) {
|
||||
originDisplayMap[message.msgID] = displayString;
|
||||
}
|
||||
}
|
||||
|
||||
if (cellDataList.count == 0) {
|
||||
callback(@{});
|
||||
return;
|
||||
}
|
||||
|
||||
TUIMessageDataProvider *provider = [[TUIMessageDataProvider alloc] init];
|
||||
NSArray *additionUserIDList = [provider getUserIDListForAdditionalUserInfo:cellDataList];
|
||||
if (additionUserIDList.count == 0) {
|
||||
callback(@{});
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary *result = [NSMutableDictionary dictionary];
|
||||
[provider
|
||||
requestForAdditionalUserInfo:cellDataList
|
||||
callback:^{
|
||||
for (TUIMessageCellData *cellData in cellDataList) {
|
||||
[cellData.additionalUserInfoResult
|
||||
enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, TUIRelationUserModel *_Nonnull obj, BOOL *_Nonnull stop) {
|
||||
NSString *str = [NSString stringWithFormat:@"{%@}", key];
|
||||
NSString *showName = obj.userID;
|
||||
if (obj.nameCard.length > 0) {
|
||||
showName = obj.nameCard;
|
||||
} else if (obj.friendRemark.length > 0) {
|
||||
showName = obj.friendRemark;
|
||||
} else if (obj.nickName.length > 0) {
|
||||
showName = obj.nickName;
|
||||
}
|
||||
|
||||
NSString *displayString = [originDisplayMap objectForKey:cellData.msgID];
|
||||
if (displayString && [displayString containsString:str]) {
|
||||
displayString = [displayString stringByReplacingOccurrencesOfString:str withString:showName];
|
||||
result[cellData.msgID] = displayString;
|
||||
}
|
||||
|
||||
callback(result);
|
||||
}];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+ (nullable NSString *)getDisplayString:(V2TIMMessage *)message {
|
||||
BOOL hasRiskContent = message.hasRiskContent;
|
||||
BOOL isRevoked = (message.status == V2TIM_MSG_STATUS_LOCAL_REVOKED);
|
||||
if (hasRiskContent && !isRevoked) {
|
||||
return TIMCommonLocalizableString(TUIKitMessageDisplayRiskContent);
|
||||
}
|
||||
NSString *str = [self parseDisplayStringFromMessageStatus:message];
|
||||
if (str == nil) {
|
||||
str = [self parseDisplayStringFromMessageElement:message];
|
||||
}
|
||||
|
||||
if (str == nil) {
|
||||
NSLog(@"current message will be ignored in chat page or conversation list page, msg:%@", message);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
+ (nullable NSString *)parseDisplayStringFromMessageStatus:(V2TIMMessage *)message {
|
||||
NSString *str = nil;
|
||||
if (message.status == V2TIM_MSG_STATUS_LOCAL_REVOKED) {
|
||||
str = [self getRevokeDispayString:message];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
+ (nullable NSString *)parseDisplayStringFromMessageElement:(V2TIMMessage *)message {
|
||||
NSString *str = nil;
|
||||
switch (message.elemType) {
|
||||
case V2TIM_ELEM_TYPE_TEXT: {
|
||||
str = [TUITextMessageCellData getDisplayString:message];
|
||||
} break;
|
||||
case V2TIM_ELEM_TYPE_IMAGE: {
|
||||
str = [TUIImageMessageCellData getDisplayString:message];
|
||||
} break;
|
||||
case V2TIM_ELEM_TYPE_SOUND: {
|
||||
str = [TUIVoiceMessageCellData getDisplayString:message];
|
||||
} break;
|
||||
case V2TIM_ELEM_TYPE_VIDEO: {
|
||||
str = [TUIVideoMessageCellData getDisplayString:message];
|
||||
} break;
|
||||
case V2TIM_ELEM_TYPE_FILE: {
|
||||
str = [TUIFileMessageCellData getDisplayString:message];
|
||||
} break;
|
||||
case V2TIM_ELEM_TYPE_FACE: {
|
||||
str = [TUIFaceMessageCellData getDisplayString:message];
|
||||
} break;
|
||||
case V2TIM_ELEM_TYPE_MERGER: {
|
||||
str = [TUIMergeMessageCellData getDisplayString:message];
|
||||
} break;
|
||||
case V2TIM_ELEM_TYPE_GROUP_TIPS: {
|
||||
str = [self getGroupTipsDisplayString:message];
|
||||
} break;
|
||||
case V2TIM_ELEM_TYPE_CUSTOM: {
|
||||
str = [self getCustomDisplayString:message];
|
||||
} break;
|
||||
default:
|
||||
str = TIMCommonLocalizableString(TUIKitMessageTipsUnsupportCustomMessage);
|
||||
break;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
+ (nullable NSString *)getCustomDisplayString:(V2TIMMessage *)message {
|
||||
// ************************************************************************************
|
||||
// ************************************************************************************
|
||||
// ************** TUICallKit , TUICallKit *************
|
||||
// ************************************************************************************
|
||||
// ************************************************************************************
|
||||
NSString *str = nil;
|
||||
id<TUIChatCallingInfoProtocol> callingInfo = nil;
|
||||
if ([self.callingDataProvider isCallingMessage:message callingInfo:&callingInfo]) {
|
||||
// Voice-video-call message
|
||||
if (callingInfo) {
|
||||
// Supported voice-video-call message
|
||||
if (callingInfo.excludeFromHistory) {
|
||||
// This message will be ignore in chat page
|
||||
str = nil;
|
||||
} else {
|
||||
// Get display text
|
||||
str = callingInfo.content ?: TIMCommonLocalizableString(TUIKitMessageTipsUnsupportCustomMessage);
|
||||
}
|
||||
} else {
|
||||
// Unsupported voice-video-call message
|
||||
str = TIMCommonLocalizableString(TUIKitMessageTipsUnsupportCustomMessage);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
// ************************************************************************************
|
||||
// ************************************************************************************
|
||||
// ************************************************************************************
|
||||
// ************************************************************************************
|
||||
|
||||
NSString *businessID = nil;
|
||||
BOOL excludeFromHistory = NO;
|
||||
|
||||
V2TIMSignalingInfo *signalingInfo = [V2TIMManager.sharedInstance getSignallingInfo:message];
|
||||
if (signalingInfo) {
|
||||
// This message is signaling message
|
||||
excludeFromHistory = message.isExcludedFromLastMessage && message.isExcludedFromUnreadCount;
|
||||
businessID = [self getSignalingBusinessID:signalingInfo];
|
||||
} else {
|
||||
// This message is normal custom message
|
||||
excludeFromHistory = NO;
|
||||
businessID = [self getCustomBusinessID:message];
|
||||
}
|
||||
|
||||
if (excludeFromHistory) {
|
||||
// Return nil means not display int the chat page
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (businessID.length > 0) {
|
||||
Class cellDataClass = nil;
|
||||
if (gDataSourceClass && [gDataSourceClass respondsToSelector:@selector(onGetCustomMessageCellDataClass:)]) {
|
||||
cellDataClass = [gDataSourceClass onGetCustomMessageCellDataClass:businessID];
|
||||
}
|
||||
if (cellDataClass && [cellDataClass respondsToSelector:@selector(getDisplayString:)]) {
|
||||
return [cellDataClass getDisplayString:message];
|
||||
}
|
||||
// In CustomerService scenarios, unsupported messages are not displayed directly.
|
||||
if ([businessID tui_containsString:BussinessID_CustomerService]) {
|
||||
return nil;
|
||||
}
|
||||
return TIMCommonLocalizableString(TUIKitMessageTipsUnsupportCustomMessage);
|
||||
} else {
|
||||
return TIMCommonLocalizableString(TUIKitMessageTipsUnsupportCustomMessage);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Data source operate
|
||||
- (void)processQuoteMessage:(NSArray<TUIMessageCellData *> *)uiMsgs {
|
||||
if (uiMsgs.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@weakify(self);
|
||||
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
|
||||
dispatch_group_async(group, concurrentQueue, ^{
|
||||
for (TUIMessageCellData *cellData in uiMsgs) {
|
||||
if (![cellData isKindOfClass:TUIReplyMessageCellData.class]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TUIReplyMessageCellData *myData = (TUIReplyMessageCellData *)cellData;
|
||||
__weak typeof(myData) weakMyData = myData;
|
||||
myData.onFinish = ^{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSUInteger index = [self.uiMsgs indexOfObject:weakMyData];
|
||||
if (index != NSNotFound) {
|
||||
// if messageData exist In datasource, reload this data.
|
||||
[UIView performWithoutAnimation:^{
|
||||
@strongify(self);
|
||||
[self.dataSource dataProviderDataSourceWillChange:self];
|
||||
[self.dataSource dataProviderDataSourceChange:self
|
||||
withType:TUIMessageBaseDataProviderDataSourceChangeTypeReload
|
||||
atIndex:index
|
||||
animation:NO];
|
||||
[self.dataSource dataProviderDataSourceDidChange:self];
|
||||
}];
|
||||
}
|
||||
});
|
||||
};
|
||||
dispatch_group_enter(group);
|
||||
[self loadOriginMessageFromReplyData:myData
|
||||
dealCallback:^{
|
||||
dispatch_group_leave(group);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSUInteger index = [self.uiMsgs indexOfObject:weakMyData];
|
||||
if (index != NSNotFound) {
|
||||
// if messageData exist In datasource, reload this data.
|
||||
[UIView performWithoutAnimation:^{
|
||||
@strongify(self);
|
||||
[self.dataSource dataProviderDataSourceWillChange:self];
|
||||
[self.dataSource dataProvider:self onRemoveHeightCache:weakMyData];
|
||||
[self.dataSource dataProviderDataSourceChange:self
|
||||
withType:TUIMessageBaseDataProviderDataSourceChangeTypeReload
|
||||
atIndex:index
|
||||
animation:NO];
|
||||
[self.dataSource dataProviderDataSourceDidChange:self];
|
||||
}];
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
});
|
||||
|
||||
dispatch_group_notify(group, dispatch_get_main_queue(),
|
||||
^{
|
||||
// complete
|
||||
});
|
||||
}
|
||||
|
||||
- (void)deleteUIMsgs:(NSArray<TUIMessageCellData *> *)uiMsgs SuccBlock:(nullable V2TIMSucc)succ FailBlock:(nullable V2TIMFail)fail {
|
||||
NSMutableArray *uiMsgList = [NSMutableArray array];
|
||||
NSMutableArray *imMsgList = [NSMutableArray array];
|
||||
for (TUIMessageCellData *uiMsg in uiMsgs) {
|
||||
if ([self.uiMsgs containsObject:uiMsg]) {
|
||||
// Check content cell
|
||||
[uiMsgList addObject:uiMsg];
|
||||
[imMsgList addObject:uiMsg.innerMessage];
|
||||
|
||||
// Check time cell which also need to be deleted
|
||||
NSInteger index = [self.uiMsgs indexOfObject:uiMsg];
|
||||
index--;
|
||||
if (index >= 0 && index < self.uiMsgs.count && [[self.uiMsgs objectAtIndex:index] isKindOfClass:TUISystemMessageCellData.class]) {
|
||||
TUISystemMessageCellData *systemCellData = (TUISystemMessageCellData *)[self.uiMsgs objectAtIndex:index];
|
||||
if (systemCellData.type == TUISystemMessageTypeDate) {
|
||||
[uiMsgList addObject:systemCellData];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (imMsgList.count == 0) {
|
||||
if (fail) {
|
||||
fail(ERR_INVALID_PARAMETERS, @"not found uiMsgs");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@weakify(self);
|
||||
[self.class deleteMessages:imMsgList
|
||||
succ:^{
|
||||
@strongify(self);
|
||||
[self.dataSource dataProviderDataSourceWillChange:self];
|
||||
for (TUIMessageCellData *uiMsg in uiMsgList) {
|
||||
NSInteger index = [self.uiMsgs indexOfObject:uiMsg];
|
||||
[self.dataSource dataProviderDataSourceChange:self
|
||||
withType:TUIMessageBaseDataProviderDataSourceChangeTypeDelete
|
||||
atIndex:index
|
||||
animation:YES];
|
||||
}
|
||||
[self removeUIMsgList:uiMsgList];
|
||||
|
||||
[self.dataSource dataProviderDataSourceDidChange:self];
|
||||
if (succ) {
|
||||
succ();
|
||||
}
|
||||
}
|
||||
fail:fail];
|
||||
}
|
||||
|
||||
- (void)removeUIMsgList:(NSArray<TUIMessageCellData *> *)cellDatas {
|
||||
for (TUIMessageCellData *uiMsg in cellDatas) {
|
||||
[self removeUIMsg:uiMsg];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Utils
|
||||
+ (nullable NSString *)getCustomBusinessID:(V2TIMMessage *)message {
|
||||
if (message == nil || message.customElem.data == nil) {
|
||||
return nil;
|
||||
}
|
||||
NSError *error = nil;
|
||||
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data options:NSJSONReadingAllowFragments error:&error];
|
||||
if (error) {
|
||||
NSLog(@"parse customElem data error: %@", error);
|
||||
return nil;
|
||||
}
|
||||
if (!param || ![param isKindOfClass:[NSDictionary class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *businessID = param[BussinessID];
|
||||
if ([businessID isKindOfClass:[NSString class]] && businessID.length > 0) {
|
||||
return businessID;
|
||||
} else {
|
||||
if ([param.allKeys containsObject:BussinessID_CustomerService]) {
|
||||
NSString *src = param[BussinessID_Src_CustomerService];
|
||||
if (src.length > 0 && [src isKindOfClass:[NSString class]]) {
|
||||
return [NSString stringWithFormat:@"%@%@", BussinessID_CustomerService, src];
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
+ (nullable NSString *)getSignalingBusinessID:(V2TIMSignalingInfo *)signalInfo {
|
||||
if (signalInfo.data == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:[signalInfo.data dataUsingEncoding:NSUTF8StringEncoding]
|
||||
options:NSJSONReadingAllowFragments
|
||||
error:&error];
|
||||
if (error) {
|
||||
NSLog(@"parse customElem data error: %@", error);
|
||||
return nil;
|
||||
}
|
||||
if (!param || ![param isKindOfClass:[NSDictionary class]]) {
|
||||
return nil;
|
||||
}
|
||||
NSString *businessID = param[BussinessID];
|
||||
if (!businessID || ![businessID isKindOfClass:[NSString class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return businessID;
|
||||
}
|
||||
|
||||
#pragma mark - TUICallKit
|
||||
|
||||
static TUIChatCallingDataProvider *gCallingDataProvider;
|
||||
+ (TUIChatCallingDataProvider *)callingDataProvider {
|
||||
if (gCallingDataProvider == nil) {
|
||||
gCallingDataProvider = [[TUIChatCallingDataProvider alloc] init];
|
||||
}
|
||||
return gCallingDataProvider;
|
||||
}
|
||||
|
||||
+ (TUIMessageCellData *)getCallingCellData:(id<TUIChatCallingInfoProtocol>)callingInfo {
|
||||
TMsgDirection direction = MsgDirectionIncoming;
|
||||
if (callingInfo.direction == TUICallMessageDirectionIncoming) {
|
||||
direction = MsgDirectionIncoming;
|
||||
} else if (callingInfo.direction == TUICallMessageDirectionOutgoing) {
|
||||
direction = MsgDirectionOutgoing;
|
||||
}
|
||||
|
||||
if (callingInfo.participantType == TUICallParticipantTypeC2C) {
|
||||
TUITextMessageCellData *cellData = [[TUITextMessageCellData alloc] initWithDirection:direction];
|
||||
if (callingInfo.streamMediaType == TUICallStreamMediaTypeVoice) {
|
||||
cellData.isAudioCall = YES;
|
||||
} else if (callingInfo.streamMediaType == TUICallStreamMediaTypeVideo) {
|
||||
cellData.isVideoCall = YES;
|
||||
} else {
|
||||
cellData.isAudioCall = NO;
|
||||
cellData.isVideoCall = NO;
|
||||
}
|
||||
cellData.content = callingInfo.content;
|
||||
cellData.isCaller = (callingInfo.participantRole == TUICallParticipantRoleCaller);
|
||||
cellData.showUnreadPoint = callingInfo.showUnreadPoint;
|
||||
cellData.isUseMsgReceiverAvatar = callingInfo.isUseReceiverAvatar;
|
||||
cellData.reuseId = TTextMessageCell_ReuseId;
|
||||
return cellData;
|
||||
} else if (callingInfo.participantType == TUICallParticipantTypeGroup) {
|
||||
TUISystemMessageCellData *cellData = [[TUISystemMessageCellData alloc] initWithDirection:direction];
|
||||
cellData.content = callingInfo.content;
|
||||
cellData.replacedUserIDList = callingInfo.participantIDList;
|
||||
cellData.reuseId = TSystemMessageCell_ReuseId;
|
||||
return cellData;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import "TUIMessageBaseMediaDataProvider.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMessageMediaDataProvider : TUIMessageBaseMediaDataProvider
|
||||
+ (TUIMessageCellData *)getMediaCellData:(V2TIMMessage *)message;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// TUIMessageSearchDataProvider.m
|
||||
// TXIMSDK_TUIKit_iOS
|
||||
//
|
||||
// Created by kayev on 2021/7/8.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMessageMediaDataProvider.h"
|
||||
#import "TUIImageMessageCellData.h"
|
||||
#import "TUIMessageBaseDataProvider+ProtectedAPI.h"
|
||||
#import "TUIVideoMessageCellData.h"
|
||||
|
||||
@implementation TUIMessageMediaDataProvider
|
||||
|
||||
+ (TUIMessageCellData *)getMediaCellData:(V2TIMMessage *)message {
|
||||
if (message.status == V2TIM_MSG_STATUS_HAS_DELETED || message.status == V2TIM_MSG_STATUS_LOCAL_REVOKED) {
|
||||
return nil;
|
||||
}
|
||||
TUIMessageCellData *data = nil;
|
||||
if (message.elemType == V2TIM_ELEM_TYPE_IMAGE) {
|
||||
data = [TUIImageMessageCellData getCellData:message];
|
||||
} else if (message.elemType == V2TIM_ELEM_TYPE_VIDEO) {
|
||||
data = [TUIVideoMessageCellData getCellData:message];
|
||||
}
|
||||
if (data) {
|
||||
data.innerMessage = message;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// TUIMessageSearchDataProvider.h
|
||||
// TXIMSDK_TUIKit_iOS
|
||||
//
|
||||
// Created by kayev on 2021/7/8.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMessageDataProvider.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMessageSearchDataProvider : TUIMessageDataProvider
|
||||
|
||||
@property(nonatomic) BOOL isOlderNoMoreMsg;
|
||||
@property(nonatomic) BOOL isNewerNoMoreMsg;
|
||||
@property(nonatomic) V2TIMMessage *msgForOlderGet;
|
||||
@property(nonatomic) V2TIMMessage *msgForNewerGet;
|
||||
|
||||
- (void)loadMessageWithSearchMsg:(V2TIMMessage *)searchMsg
|
||||
SearchMsgSeq:(uint64_t)searchSeq
|
||||
ConversationInfo:(TUIChatConversationModel *)conversation
|
||||
SucceedBlock:(void (^)(BOOL isOlderNoMoreMsg, BOOL isNewerNoMoreMsg, NSArray<TUIMessageCellData *> *newMsgs))succeedBlock
|
||||
FailBlock:(V2TIMFail)failBlock;
|
||||
|
||||
- (void)loadMessageWithIsRequestOlderMsg:(BOOL)orderType
|
||||
ConversationInfo:(TUIChatConversationModel *)conversation
|
||||
SucceedBlock:(void (^)(BOOL isOlderNoMoreMsg, BOOL isNewerNoMoreMsg, BOOL isFirstLoad,
|
||||
NSArray<TUIMessageCellData *> *newUIMsgs))succeedBlock
|
||||
FailBlock:(V2TIMFail)failBlock;
|
||||
|
||||
- (void)removeAllSearchData;
|
||||
|
||||
- (void)findMessages:(NSArray<NSString *> *)msgIDs callback:(void (^)(BOOL success, NSString *desc, NSArray<V2TIMMessage *> *messages))callback;
|
||||
|
||||
- (void)preProcessMessage:(NSArray *)uiMsgs;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,361 @@
|
||||
//
|
||||
// TUIMessageSearchDataProvider.m
|
||||
// TXIMSDK_TUIKit_iOS
|
||||
//
|
||||
// Created by kayev on 2021/7/8.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMessageSearchDataProvider.h"
|
||||
#import "TUIMessageBaseDataProvider+ProtectedAPI.h"
|
||||
#import "TUIChatMediaSendingManager.h"
|
||||
typedef void (^LoadSearchMsgSucceedBlock)(BOOL isOlderNoMoreMsg, BOOL isNewerNoMoreMsg, NSArray<TUIMessageCellData *> *newMsgs);
|
||||
typedef void (^LoadMsgSucceedBlock)(BOOL isOlderNoMoreMsg, BOOL isNewerNoMoreMsg, BOOL isFirstLoad, NSArray<TUIMessageCellData *> *newUIMsgs);
|
||||
|
||||
@interface TUIMessageSearchDataProvider ()
|
||||
|
||||
@property(nonatomic, copy) LoadSearchMsgSucceedBlock loadSearchMsgSucceedBlock;
|
||||
@property(nonatomic, copy) LoadMsgSucceedBlock loadMsgSucceedBlock;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMessageSearchDataProvider
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_isOlderNoMoreMsg = NO;
|
||||
_isNewerNoMoreMsg = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)loadMessageWithSearchMsg:(V2TIMMessage *)searchMsg
|
||||
SearchMsgSeq:(uint64_t)searchSeq
|
||||
ConversationInfo:(TUIChatConversationModel *)conversation
|
||||
SucceedBlock:(void (^)(BOOL isOlderNoMoreMsg, BOOL isNewerNoMoreMsg, NSArray<TUIMessageCellData *> *newMsgs))succeedBlock
|
||||
FailBlock:(V2TIMFail)failBlock {
|
||||
if (self.isLoadingData) {
|
||||
failBlock(ERR_SUCC, @"refreshing");
|
||||
return;
|
||||
}
|
||||
self.isLoadingData = YES;
|
||||
self.isOlderNoMoreMsg = NO;
|
||||
self.isNewerNoMoreMsg = NO;
|
||||
self.loadSearchMsgSucceedBlock = succeedBlock;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Load the oldest pageCount messages starting from locating message
|
||||
*/
|
||||
{
|
||||
dispatch_group_enter(group);
|
||||
V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init];
|
||||
option.getType = V2TIM_GET_CLOUD_OLDER_MSG;
|
||||
option.count = self.pageCount;
|
||||
option.groupID = conversation.groupID;
|
||||
option.userID = conversation.userID;
|
||||
if (searchMsg) {
|
||||
option.lastMsg = searchMsg;
|
||||
} else {
|
||||
option.lastMsgSeq = searchSeq;
|
||||
}
|
||||
[V2TIMManager.sharedInstance getHistoryMessageList:option
|
||||
succ:^(NSArray<V2TIMMessage *> *msgs) {
|
||||
msgs = msgs.reverseObjectEnumerator.allObjects;
|
||||
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 pageCount messages starting from the locating message
|
||||
*/
|
||||
{
|
||||
dispatch_group_enter(group);
|
||||
V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init];
|
||||
option.getType = V2TIM_GET_CLOUD_NEWER_MSG;
|
||||
option.count = self.pageCount;
|
||||
option.groupID = conversation.groupID;
|
||||
option.userID = conversation.userID;
|
||||
if (searchMsg) {
|
||||
option.lastMsg = searchMsg;
|
||||
} else {
|
||||
option.lastMsgSeq = searchSeq;
|
||||
}
|
||||
[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;
|
||||
|
||||
NSMutableArray *results = [NSMutableArray array];
|
||||
[results addObjectsFromArray:olders];
|
||||
if (searchMsg) {
|
||||
/**
|
||||
* Pulling messages through the msg will not return the msg object itself, here you need to actively add the msg to the results list
|
||||
*/
|
||||
[results addObject:searchMsg];
|
||||
} else {
|
||||
/**
|
||||
* Pulling messages through the msg seq, pulling old messages and new messages will return the msg object itself, here you need to deduplicate the msg
|
||||
* object in results
|
||||
*/
|
||||
[results removeLastObject];
|
||||
}
|
||||
[results addObjectsFromArray:newers];
|
||||
self.msgForOlderGet = results.firstObject;
|
||||
self.msgForNewerGet = results.lastObject;
|
||||
|
||||
@weakify(self);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@strongify(self);
|
||||
[self.heightCache_ removeAllObjects];
|
||||
[self.uiMsgs_ removeAllObjects];
|
||||
|
||||
NSArray *msgs = results.reverseObjectEnumerator.allObjects;
|
||||
NSMutableArray *uiMsgs = [self transUIMsgFromIMMsg:msgs];
|
||||
if (uiMsgs.count == 0) {
|
||||
return;
|
||||
}
|
||||
[self getGroupMessageReceipts:msgs
|
||||
uiMsgs:uiMsgs
|
||||
succ:^{
|
||||
[self preProcessMessage:uiMsgs];
|
||||
}
|
||||
fail:^{
|
||||
[self preProcessMessage:uiMsgs];
|
||||
}];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)loadMessageWithIsRequestOlderMsg:(BOOL)orderType
|
||||
ConversationInfo:(TUIChatConversationModel *)conversation
|
||||
SucceedBlock:(void (^)(BOOL isOlderNoMoreMsg, BOOL isNewerNoMoreMsg, BOOL isFirstLoad,
|
||||
NSArray<TUIMessageCellData *> *newUIMsgs))succeedBlock
|
||||
FailBlock:(V2TIMFail)failBlock {
|
||||
self.isLoadingData = YES;
|
||||
self.loadMsgSucceedBlock = succeedBlock;
|
||||
|
||||
int requestCount = self.pageCount;
|
||||
V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init];
|
||||
option.userID = conversation.userID;
|
||||
option.groupID = conversation.groupID;
|
||||
option.getType = orderType ? V2TIM_GET_CLOUD_OLDER_MSG : V2TIM_GET_CLOUD_NEWER_MSG;
|
||||
option.count = requestCount;
|
||||
option.lastMsg = orderType ? self.msgForOlderGet : self.msgForNewerGet;
|
||||
@weakify(self);
|
||||
[V2TIMManager.sharedInstance getHistoryMessageList:option
|
||||
succ:^(NSArray<V2TIMMessage *> *msgs) {
|
||||
@strongify(self);
|
||||
if (!orderType) {
|
||||
msgs = msgs.reverseObjectEnumerator.allObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the lastMsg flag
|
||||
* -- The current pull operation is to pull from the latest time point to the past
|
||||
*/
|
||||
BOOL isLastest = (self.msgForNewerGet == nil) && (self.msgForOlderGet == nil) && orderType;
|
||||
if (msgs.count != 0) {
|
||||
if (orderType) {
|
||||
self.msgForOlderGet = msgs.lastObject;
|
||||
if (self.msgForNewerGet == nil) {
|
||||
self.msgForNewerGet = msgs.firstObject;
|
||||
}
|
||||
} else {
|
||||
if (self.msgForOlderGet == nil) {
|
||||
self.msgForOlderGet = msgs.lastObject;
|
||||
}
|
||||
self.msgForNewerGet = msgs.firstObject;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update no data flag
|
||||
*/
|
||||
if (msgs.count < requestCount) {
|
||||
if (orderType) {
|
||||
self.isOlderNoMoreMsg = YES;
|
||||
} else {
|
||||
self.isNewerNoMoreMsg = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (isLastest) {
|
||||
/**
|
||||
* The current pull operation is to pull from the latest time point to the past
|
||||
*/
|
||||
self.isNewerNoMoreMsg = YES;
|
||||
}
|
||||
|
||||
NSMutableArray<TUIMessageCellData *> *uiMsgs = [self transUIMsgFromIMMsg:msgs];
|
||||
if (uiMsgs.count == 0) {
|
||||
if (self.loadMsgSucceedBlock) {
|
||||
self.loadMsgSucceedBlock(self.isOlderNoMoreMsg, self.isNewerNoMoreMsg, self.isFirstLoad, uiMsgs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//add media placeholder celldata
|
||||
if (self.conversationModel.conversationID.length > 0) {
|
||||
NSMutableArray<TUIChatMediaTask *> * tasks = [TUIChatMediaSendingManager.sharedInstance
|
||||
findPlaceHolderListByConversationID:self.conversationModel.conversationID];
|
||||
for (TUIChatMediaTask * task in tasks) {
|
||||
if (task.placeHolderCellData) {
|
||||
[uiMsgs addObject:task.placeHolderCellData];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[self getGroupMessageReceipts:msgs
|
||||
uiMsgs:uiMsgs
|
||||
succ:^{
|
||||
[self preProcessMessage:uiMsgs orderType:orderType];
|
||||
}
|
||||
fail:^{
|
||||
[self preProcessMessage:uiMsgs orderType:orderType];
|
||||
}];
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
self.isLoadingData = NO;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)getGroupMessageReceipts:(NSArray *)msgs uiMsgs:(NSArray *)uiMsgs succ:(void (^)(void))succBlock fail:(void (^)(void))failBlock {
|
||||
[[V2TIMManager sharedInstance] getMessageReadReceipts:msgs
|
||||
succ:^(NSArray<V2TIMMessageReceipt *> *receiptList) {
|
||||
NSLog(@"getGroupMessageReceipts succeed, receiptList: %@", receiptList);
|
||||
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
|
||||
for (V2TIMMessageReceipt *receipt in receiptList) {
|
||||
[dict setObject:receipt forKey:receipt.msgID];
|
||||
}
|
||||
for (TUIMessageCellData *data in uiMsgs) {
|
||||
V2TIMMessageReceipt *receipt = dict[data.msgID];
|
||||
data.messageReceipt = receipt;
|
||||
}
|
||||
|
||||
if (succBlock) {
|
||||
succBlock();
|
||||
}
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
NSLog(@"getGroupMessageReceipts failed, code: %d, desc: %@", code, desc);
|
||||
if (failBlock) {
|
||||
failBlock();
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)preProcessMessage:(NSArray *)uiMsgs {
|
||||
@weakify(self);
|
||||
[self preProcessMessage:uiMsgs
|
||||
callback:^{
|
||||
@strongify(self);
|
||||
[self addUIMsgs:uiMsgs];
|
||||
self.loadSearchMsgSucceedBlock(self.isOlderNoMoreMsg, self.isNewerNoMoreMsg, self.uiMsgs_);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)preProcessMessage:(NSArray *)uiMsgs orderType:(BOOL)orderType {
|
||||
@weakify(self);
|
||||
[self preProcessMessage:uiMsgs
|
||||
callback:^{
|
||||
@strongify(self);
|
||||
|
||||
if (orderType) {
|
||||
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, uiMsgs.count)];
|
||||
[self insertUIMsgs:uiMsgs atIndexes:indexSet];
|
||||
} else {
|
||||
[self addUIMsgs:uiMsgs];
|
||||
}
|
||||
|
||||
if (self.loadMsgSucceedBlock) {
|
||||
self.loadMsgSucceedBlock(self.isOlderNoMoreMsg, self.isNewerNoMoreMsg, self.isFirstLoad, uiMsgs);
|
||||
}
|
||||
|
||||
self.isLoadingData = NO;
|
||||
self.isFirstLoad = NO;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)removeAllSearchData {
|
||||
[self.uiMsgs_ removeAllObjects];
|
||||
self.isNewerNoMoreMsg = NO;
|
||||
self.isOlderNoMoreMsg = NO;
|
||||
self.isFirstLoad = YES;
|
||||
self.msgForNewerGet = nil;
|
||||
self.msgForOlderGet = nil;
|
||||
self.loadSearchMsgSucceedBlock = nil;
|
||||
}
|
||||
|
||||
- (void)findMessages:(NSArray<NSString *> *)msgIDs callback:(void (^)(BOOL success, NSString *desc, NSArray<V2TIMMessage *> *messages))callback {
|
||||
[V2TIMManager.sharedInstance findMessages:msgIDs
|
||||
succ:^(NSArray<V2TIMMessage *> *msgs) {
|
||||
if (callback) {
|
||||
callback(YES, @"", msgs);
|
||||
}
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
if (callback) {
|
||||
callback(NO, desc, nil);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Override
|
||||
- (void)onRecvNewMessage:(V2TIMMessage *)msg {
|
||||
if (self.isNewerNoMoreMsg == NO) {
|
||||
/**
|
||||
* If the current message list has not pulled the last message, ignore the new message;
|
||||
* If it is processed at this time, it will cause new messages to be added to the history list, resulting in the problem of position confusion.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
if (self.dataSource.isDataSourceConsistent == NO ) {
|
||||
self.isNewerNoMoreMsg = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
[super onRecvNewMessage:msg];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user