增加换肤功能

This commit is contained in:
启星
2025-08-14 10:07:49 +08:00
parent f6964c1e89
commit 4f9318d98e
8789 changed files with 978530 additions and 2 deletions

View File

@@ -0,0 +1,23 @@
//
// TUIEmojiMessageReactPreLoadProvider.h
// TUIEmojiPlugin
//
// Created by cologne on 2023/12/26.
//
#import <Foundation/Foundation.h>
#import "TUIReactModel.h"
#import <TIMCommon/TUIMessageCellData.h>
NS_ASSUME_NONNULL_BEGIN
@import ImSDK_Plus;
@interface TUIEmojiMessageReactPreLoadProvider : NSObject
- (void)getMessageReactions:(NSArray<TUIMessageCellData *> *)cellDataList
maxUserCountPerReaction:(uint32_t)maxUserCountPerReaction
succ:(V2TIMSucc)succ
fail:(V2TIMFail)fail;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,67 @@
//
// TUIEmojiMessageReactPreLoadProvider.m
// TUIEmojiPlugin
//
// Created by cologne on 2023/12/26.
//
#import "TUIEmojiMessageReactPreLoadProvider.h"
#import <TIMCommon/NSString+TUIEmoji.h>
#import <TIMCommon/TIMDefine.h>
#import <TIMCommon/TUIMessageCellData.h>
#import "TUIMessageCellData+Reaction.h"
@implementation TUIEmojiMessageReactPreLoadProvider
- (void)getMessageReactions:(NSArray<TUIMessageCellData *> *)cellDataList
maxUserCountPerReaction:(uint32_t)maxUserCountPerReaction
succ:(V2TIMSucc)succ
fail:(V2TIMFail)fail {
NSMutableArray <V2TIMMessage *> *messageList = [NSMutableArray array];
NSMutableDictionary *cellDataMap = [NSMutableDictionary dictionary];
for (TUIMessageCellData *cellData in cellDataList) {
if (cellData.innerMessage) {
if (cellData.innerMessage.status != V2TIM_MSG_STATUS_SEND_FAIL) {
[messageList addObject:cellData.innerMessage];
}
}
if (cellData.msgID) {
[cellDataMap setObject:cellData forKey:cellData.msgID];
}
if (!cellData.reactdataProvider) {
[cellData setupReactDataProvider];
}
}
__weak typeof(self)weakSelf = self;
[[V2TIMManager sharedInstance] getMessageReactions:messageList maxUserCountPerReaction:maxUserCountPerReaction
succ:^(NSArray<V2TIMMessageReactionResult *> *resultList) {
//
for (V2TIMMessageReactionResult *result in resultList) {
int32_t resultCode = result.resultCode;
NSString *msgID = result.msgID;
NSString *resultInfo = result.resultInfo;
NSArray *reactionList = result.reactionList;
for (V2TIMMessageReaction *reaction in reactionList) {
TUIReactModel * model = [TUIReactModel createTagsModelByReaction:reaction];
if (model) {
if (msgID) {
TUIMessageCellData *cellData = cellDataMap[msgID];
cellData.reactdataProvider.isFirsLoad = YES;
[cellData.reactdataProvider.reactMap setObject:model forKey:model.emojiKey];
[cellData.reactdataProvider.reactArray addObject:model];
}
}
}
}
if(succ) {
succ();
}
} fail:^(int code, NSString *desc) {
fail(code,desc);
}];
}
@end

View File

@@ -0,0 +1,39 @@
//
// TUIEmojiReactDataProvider.h
// TUIEmojiPlugin
//
// Created by cologne on 2023/11/23.
//
#import <Foundation/Foundation.h>
#import "TUIReactModel.h"
@import ImSDK_Plus;
NS_ASSUME_NONNULL_BEGIN
typedef void (^TUIEmojiReactGetMessageReactionsSucc)(NSArray<TUIReactModel *> *tagsArray,NSMutableDictionary *tagsMap);
typedef void (^TUIEmojiReactMessageReactionsChanged)(NSArray<TUIReactModel *> *tagsArray,NSMutableDictionary *tagsMap);
@interface TUIEmojiReactDataProvider : NSObject
@property (nonatomic,copy) NSString * msgId;
@property (nonatomic,copy) TUIEmojiReactMessageReactionsChanged changed;
@property (nonatomic,strong) NSMutableDictionary *reactMap;
@property (nonatomic,strong) NSMutableArray *reactArray;
@property (nonatomic,assign) BOOL isFirsLoad;
- (TUIReactModel *)getCurrentReactionIDInMap:(NSString *)reactionID;
- (void)addMessageReaction:(V2TIMMessage *)v2Message
reactionID:(NSString *)reactionID
succ:(V2TIMSucc)succ
fail:(V2TIMFail)fail;
- (void)removeMessageReaction:(V2TIMMessage *)v2Message
reactionID:(NSString *)reactionID
succ:(V2TIMSucc)succ
fail:(V2TIMFail)fail;
- (void)getMessageReactions:(NSArray<V2TIMMessage *> *)messageList
maxUserCountPerReaction:(uint32_t)maxUserCountPerReaction
succ:(TUIEmojiReactGetMessageReactionsSucc)succ
fail:(V2TIMFail)fail;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,175 @@
//
// TUIEmojiReactDataProvider.m
// TUIEmojiPlugin
//
// Created by cologne on 2023/11/23.
//
#import "TUIEmojiReactDataProvider.h"
#import <TIMCommon/TIMDefine.h>
@interface TUIEmojiReactDataProvider() <V2TIMAdvancedMsgListener>
@end
@implementation TUIEmojiReactDataProvider
- (instancetype)init {
self = [super init];
if (self) {
[self setupNotify];
}
return self;
}
- (void)setupNotify {
[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];
}
- (NSMutableArray *)reactArray {
if (!_reactArray) {
_reactArray = [NSMutableArray arrayWithCapacity:3];
}
return _reactArray;
}
- (NSMutableDictionary *)reactMap {
if (!_reactMap) {
_reactMap = [NSMutableDictionary dictionaryWithCapacity:3];
}
return _reactMap;
}
- (void)addMessageReaction:(V2TIMMessage *)v2Message
reactionID:(NSString *)reactionID
succ:(V2TIMSucc)succ
fail:(V2TIMFail)fail {
[[V2TIMManager sharedInstance] addMessageReaction:v2Message reactionID:reactionID succ:^{
if (succ){
succ();
}
} fail:^(int code, NSString *desc) {
if (fail){
fail(code,desc);
}
}];
}
- (void)removeMessageReaction:(V2TIMMessage *)v2Message
reactionID:(NSString *)reactionID
succ:(V2TIMSucc)succ
fail:(V2TIMFail)fail {
[[V2TIMManager sharedInstance] removeMessageReaction:v2Message reactionID:reactionID succ:^{
if (succ){
succ();
}
} fail:^(int code, NSString *desc) {
if (fail){
fail(code,desc);
}
}];
}
- (void)getMessageReactions:(NSArray<V2TIMMessage *> *)messageList
maxUserCountPerReaction:(uint32_t)maxUserCountPerReaction
succ:(TUIEmojiReactGetMessageReactionsSucc)succ
fail:(V2TIMFail)fail {
__block NSMutableDictionary *modifyUserMap = nil;
__block NSMutableArray *reactArray = nil;
__weak typeof(self)weakSelf = self;
self.isFirsLoad = YES;
[[V2TIMManager sharedInstance] getMessageReactions:messageList maxUserCountPerReaction:maxUserCountPerReaction
succ:^(NSArray<V2TIMMessageReactionResult *> *resultList) {
modifyUserMap = [NSMutableDictionary dictionaryWithCapacity:3];
reactArray = [NSMutableArray arrayWithCapacity:3];
// Batch pull message response information successful
for (V2TIMMessageReactionResult *result in resultList) {
int32_t resultCode = result.resultCode;
NSString *resultInfo = result.resultInfo;
NSArray *reactionList = result.reactionList;
for (V2TIMMessageReaction *reaction in reactionList) {
TUIReactModel * model = [TUIReactModel createTagsModelByReaction:reaction];
if (model) {
[modifyUserMap setObject:model forKey:model.emojiKey];
[reactArray addObject:model];
}
}
}
weakSelf.reactMap = [NSMutableDictionary dictionaryWithDictionary:modifyUserMap];
weakSelf.reactArray = [NSMutableArray arrayWithArray:reactArray];
if(succ) {
succ(reactArray,modifyUserMap);
}
} fail:^(int code, NSString *desc) {
fail(code,desc);
}];
}
- (void)onRecvMessageReactionsChanged:(NSArray<V2TIMMessageReactionChangeInfo *> *)changeList {
__block NSMutableDictionary *changedReactMap = nil;
__block NSMutableArray *changedReactArray = nil;
changedReactMap = [NSMutableDictionary dictionaryWithCapacity:3];
changedReactArray = [NSMutableArray arrayWithCapacity:3];
__weak typeof(self)weakSelf = self;
//Response to the update callback after receiving the message
for (V2TIMMessageReactionChangeInfo *changeInfo in changeList) {
NSString *msgID = changeInfo.msgID;
if (![msgID isEqualToString:self.msgId] ) {
// Not the current message
return;
}
//Changed reaction list
NSArray *reactionList = changeInfo.reactionList;
for (V2TIMMessageReaction *reaction in reactionList) {
TUIReactModel * model = [TUIReactModel createTagsModelByReaction:reaction];
if (model) {
[changedReactMap setObject:model forKey:model.emojiKey];
[changedReactArray addObject:model];
}
}
}
__block NSMutableDictionary *sortedReactMap = [NSMutableDictionary dictionaryWithDictionary:self.reactMap];
__block NSMutableArray *sortedReactArray = [NSMutableArray arrayWithArray:self.reactArray];
//deal change
[changedReactMap enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, TUIReactModel* _Nonnull changedObj, BOOL * _Nonnull stop) {
__block BOOL inOrigin = NO;
[weakSelf.reactArray enumerateObjectsUsingBlock:^(TUIReactModel* _Nonnull originObj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([originObj.emojiKey isEqualToString:changedObj.emojiKey] ) {
inOrigin = YES;
if (changedObj.totalUserCount != 0) {
sortedReactMap[originObj.emojiKey] = changedObj;
[sortedReactArray removeObject:originObj];
[sortedReactArray addObject:changedObj];
}
else {
[sortedReactMap removeObjectForKey:originObj.emojiKey];
[sortedReactArray removeObject:originObj];
}
}
}];
if (!inOrigin) {
[sortedReactArray addObject:changedObj];
[sortedReactMap setObject:changedObj forKey:changedObj.emojiKey];
}
}];
self.reactArray = [NSMutableArray arrayWithArray:sortedReactArray];
self.reactMap = [NSMutableDictionary dictionaryWithDictionary:sortedReactMap];
if (self.changed) {
self.changed(sortedReactArray, sortedReactMap);
}
}
- (TUIReactModel *)getCurrentReactionIDInMap:(NSString *)reactionID {
if ([self.reactMap objectForKey:reactionID]) {
return self.reactMap[reactionID];
}
return nil;
}
@end

View File

@@ -0,0 +1,26 @@
//
// TUIMessageCellData+Reaction.h
// TUIEmojiPlugin
//
// Created by cologne on 2023/11/27.
// Copyright © 2023 Tencent. All rights reserved.
#import <UIKit/UIKit.h>
#import <TIMCommon/TUIMessageCellData.h>
#import "TUIEmojiReactDataProvider.h"
NS_ASSUME_NONNULL_BEGIN
typedef void (^TUIReactValueChangedCallback)(NSArray<TUIReactModel *> *tagsArray);
@interface TUIMessageCellData (Reaction)
@property(nonatomic, strong) TUIEmojiReactDataProvider *reactdataProvider;
@property(nonatomic, copy) TUIReactValueChangedCallback reactValueChangedCallback;
- (void)setupReactDataProvider;
//- (void)loadReactList;
- (void)updateReactClick:(NSString *)faceName;
- (void)addReactByEmojiKey:(NSString *)emojiKey;
- (void)delReactByEmojiKey:(NSString *)emojiKey;
- (void)addReactModel:(TUIReactModel *)model;
- (void)delReactModel:(TUIReactModel *)model;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,95 @@
//
// TUIMessageCellData+Reaction.m
// TUIEmojiPlugin
//
// Created by cologne on 2023/11/27.
// Copyright © 2023 Tencent. All rights reserved.
#import "TUIMessageCellData+Reaction.h"
#import <objc/runtime.h>
#import "TUIReactModel.h"
#import "TUIReactUtil.h"
@implementation TUIMessageCellData (Reaction)
- (TUIEmojiReactDataProvider *)reactdataProvider {
return objc_getAssociatedObject(self, @"reactdataProvider");
}
- (void)setReactdataProvider:(TUIEmojiReactDataProvider *)reactdataProvider {
objc_setAssociatedObject(self, @"reactdataProvider", reactdataProvider, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)setReactValueChangedCallback:(TUIReactValueChangedCallback)reactValueChangedCallback {
objc_setAssociatedObject(self, @"reactValueChangedCallback", reactValueChangedCallback, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (TUIReactValueChangedCallback)reactValueChangedCallback {
return objc_getAssociatedObject(self, @"reactValueChangedCallback");
}
- (void)setupReactDataProvider {
if (self.status == Msg_Status_Fail) {
return;
}
self.reactdataProvider = [[TUIEmojiReactDataProvider alloc] init];
self.reactdataProvider.msgId = self.innerMessage.msgID;
__weak typeof(self) weakSelf = self;
self.reactdataProvider.changed = ^(NSArray<TUIReactModel *> * _Nonnull tagsArray, NSMutableDictionary * _Nonnull tagsMap) {
if (weakSelf.reactValueChangedCallback) {
weakSelf.reactValueChangedCallback(tagsArray);
}
};
}
- (void)addReactModel:(TUIReactModel *)model {
[self addReactByEmojiKey:model.emojiKey];
}
- (void)delReactModel:(TUIReactModel *)model {
[self delReactByEmojiKey:model.emojiKey];
}
- (void)addReactByEmojiKey:(NSString *)emojiKey {
[self.reactdataProvider addMessageReaction:self.innerMessage
reactionID:emojiKey
succ:^{
}
fail:^(int code, NSString *desc){
NSString* errMsg = [TUITool convertIMError:code msg:desc];
[TUITool makeToast:errMsg];
}];
}
- (void)delReactByEmojiKey:(NSString *)emojiKey {
[self.reactdataProvider removeMessageReaction:self.innerMessage
reactionID:emojiKey
succ:^{
}
fail:^(int code, NSString *desc) {
NSString *errMsg = [TUITool convertIMError:code msg:desc];
[TUITool makeToast:errMsg];
}];
}
- (void)updateReactClick:(NSString *)faceName {
TUIEmojiReactDataProvider *reactdataProvider = self.reactdataProvider;
TUIReactModel * targetModel = [reactdataProvider getCurrentReactionIDInMap:faceName];
if (targetModel) {
if (targetModel.reactedByMyself) {
//del
[self delReactByEmojiKey:targetModel.emojiKey];
}
else {
//add
[self addReactByEmojiKey:targetModel.emojiKey];
}
}
else {
// new model
[self addReactByEmojiKey:faceName];
}
}
@end

View File

@@ -0,0 +1,60 @@
//
// TUIReactModel.h
// TUITagDemo
//
// Created by wyl on 2022/5/12.
// Copyright © 2022 TUI. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <TIMCommon/TIMDefine.h>
#import <TIMCommon/TUIRelationUserModel.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIReactUserModel : TUIRelationUserModel
@end
@interface TUIReactModel : NSObject
+ (TUIReactModel *)createTagsModelByReaction:(V2TIMMessageReaction *)reaction;
/**
* Label name
*/
@property(nonatomic, copy) NSString *name;
/**
* Label alias
*/
@property(nonatomic, copy) NSString *alias;
/**
* The color of label displaying name
*/
@property(nonatomic, strong) UIColor *textColor;
@property(nonatomic, assign) BOOL isSelect;
@property(nonatomic, strong) UIColor *defaultColor;
@property(nonatomic, strong) UIColor *selectColor;
@property(nonatomic, copy) NSString *emojiKey;
@property(nonatomic, strong) NSMutableArray *followIDs;
@property(nonatomic, strong) NSMutableArray *followUserNames;
@property(nonatomic, strong) NSMutableArray<TUIReactUserModel *> *followUserModels;
@property(nonatomic, assign) double maxWidth;
@property(nonatomic, copy) NSString *emojiPath;
@property(nonatomic, assign) NSInteger totalUserCount;
@property(nonatomic, assign) BOOL reactedByMyself;
- (NSString *)descriptionFollowUserStr;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,92 @@
//
// TUIReactModel.m
// TUITagDemo
//
// Created by wyl on 2022/5/12.
// Copyright © 2022 TUI. All rights reserved.
//
#import "TUIReactModel.h"
#import <TIMCommon/NSString+TUIEmoji.h>
@implementation TUIReactUserModel
- (NSString *)getDisplayName {
if (IS_NOT_EMPTY_NSSTRING(self.nameCard)) {
return self.nameCard;
} else if (IS_NOT_EMPTY_NSSTRING(self.friendRemark)) {
return self.friendRemark;
} else if (IS_NOT_EMPTY_NSSTRING(self.nickName)) {
return self.nickName;
} else {
return self.userID;
}
return @"";
}
@end
@implementation TUIReactModel
- (NSMutableArray *)followIDs {
if (!_followIDs) {
_followIDs = [NSMutableArray arrayWithCapacity:3];
}
return _followIDs;
}
- (NSMutableArray *)followUserNames {
if (!_followUserNames) {
_followUserNames = [NSMutableArray arrayWithCapacity:3];
}
return _followUserNames;
}
- (NSMutableArray<TUIReactUserModel *> *)followUserModels {
if (!_followUserModels) {
_followUserModels = [NSMutableArray arrayWithCapacity:3];
}
return _followUserModels;
}
- (NSString *)descriptionFollowUserStr {
if (self.followUserNames.count <= 0) {
return @"";
}
NSString *str = [self.followUserNames componentsJoinedByString:@","];
return str;
}
+ (TUIReactModel *)createTagsModelByReaction:(V2TIMMessageReaction *)reaction {
NSString *reactionID = reaction.reactionID;
uint32_t totalUserCount = reaction.totalUserCount;
NSArray *partialUserList = reaction.partialUserList;
TUIReactModel *model = [[TUIReactModel alloc] init];
model.defaultColor = [TIMCommonDynamicColor(@"", @"#444444") colorWithAlphaComponent:0.1];
model.textColor = TIMCommonDynamicColor(@"chat_react_desc_color", @"#888888");
model.emojiKey = reactionID;
model.emojiPath = [reactionID getEmojiImagePath];
model.reactedByMyself = reaction.reactedByMyself;
model.totalUserCount = reaction.totalUserCount;
for (V2TIMUserInfo *obj in partialUserList) {
TUIReactUserModel *userModel = [[TUIReactUserModel alloc] init];
userModel.userID = obj.userID;
userModel.nickName = obj.nickName;
userModel.faceURL = obj.faceURL;
if (userModel && userModel.userID.length > 0) {
[model.followIDs addObject:obj.userID];
}
NSString *name = [userModel getDisplayName];
if (name.length > 0) {
[model.followUserNames addObject:name];
}
if (userModel) {
[model.followUserModels addObject:userModel];
}
}
return model;
}
@end

View File

@@ -0,0 +1,16 @@
//
// TUIEmojiExtensionObserver.h
// TUIEmojiPlugin
//
// Created by cologne on 2023/11/22.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIEmojiExtensionObserver : NSObject
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,328 @@
//
// TUIEmojiExtensionObserver.m
// TUIEmojiPlugin
//
// Created by cologne on 2023/11/22.
//
#import "TUIEmojiExtensionObserver.h"
#import <TIMCommon/TIMPopActionProtocol.h>
#import <TIMCommon/TUIMessageCell.h>
#import <TUIChat/TUIChatPopMenu.h>
#import <TUICore/TUICore.h>
#import <TUICore/TUIDefine.h>
#import <TUICore/UIView+TUILayout.h>
#import <TUICore/TUILogin.h>
#import "TUIReactPopRecentView.h"
#import "TUIReactPopEmojiView.h"
#import "TUIReactPreview.h"
#import "TUIReactModel.h"
#import "TUIEmojiReactDataProvider.h"
#import "TUIMessageCellData+Reaction.h"
#import "TUIReactPreview_Minimalist.h"
#import "TUIReactMembersController.h"
#import "TUIReactPopContextRecentView.h"
#import <TUIChat/TUIChatPopContextController.h>
#import "TUIEmojiMessageReactPreLoadProvider.h"
#import "TUIReactUtil.h"
@interface TUIEmojiExtensionObserver () <TUIExtensionProtocol>
@property(nonatomic, weak) UINavigationController *navVC;
@property(nonatomic, weak) TUICommonTextCellData *cellData;
@end
@implementation TUIEmojiExtensionObserver
static id gShareInstance = nil;
+ (void)load {
// UI extensions in pop menu when message is long pressed.
[TUICore registerExtension:TUICore_TUIChatExtension_ChatPopMenuReactRecentView_ClassicExtensionID object:TUIEmojiExtensionObserver.shareInstance];
[TUICore registerExtension:TUICore_TUIChatExtension_ChatPopMenuReactRecentView_MinimalistExtensionID object:TUIEmojiExtensionObserver.shareInstance];
[TUICore registerExtension:TUICore_TUIChatExtension_ChatPopMenuReactDetailView_ClassicExtensionID object:TUIEmojiExtensionObserver.shareInstance];
[TUICore registerExtension:TUICore_TUIChatExtension_ChatPopMenuReactDetailView_MinimalistExtensionID object:TUIEmojiExtensionObserver.shareInstance];
[TUICore registerExtension:TUICore_TUIChatExtension_ChatMessageReactPreview_ClassicExtensionID object:TUIEmojiExtensionObserver.shareInstance];
[TUICore registerExtension:TUICore_TUIChatExtension_ChatMessageReactPreview_MinimalistExtensionID object:TUIEmojiExtensionObserver.shareInstance];
}
+ (instancetype)shareInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
gShareInstance = [[self alloc] init];
});
return gShareInstance;
}
- (instancetype)init {
if (self = [super init]) {
[self setupNotify];
}
return self;
}
- (void)setupNotify {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(fetchReactListByCellDatas:)
name:@"TUIKitFetchReactNotification"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onLoginSucceeded) name:TUILoginSuccessNotification object:nil];
}
#pragma mark - TUIKitFetchReactNotification
- (void)fetchReactListByCellDatas:(NSNotification *)notification {
NSArray<TUIMessageCellData *> *uiMsgs = notification.object;
TUIEmojiMessageReactPreLoadProvider *preLoadProvider = [[TUIEmojiMessageReactPreLoadProvider alloc] init];
NSInteger batchSize = 20;
NSInteger totalBatches = (uiMsgs.count + batchSize - 1) / batchSize;
for (NSInteger batchIndex = 0; batchIndex < totalBatches; batchIndex++) {
NSRange batchRange = NSMakeRange(batchIndex * batchSize, MIN(batchSize, uiMsgs.count - batchIndex * batchSize));
NSArray<TUIMessageCellData *> *batchMsgs = [uiMsgs subarrayWithRange:batchRange];
[preLoadProvider getMessageReactions:batchMsgs maxUserCountPerReaction:5 succ:^{
for (TUIMessageCellData *cellData in batchMsgs) {
if (cellData.msgID) {
if (cellData.reactValueChangedCallback) {
cellData.reactValueChangedCallback(cellData.reactdataProvider.reactArray);
}
}
}
} fail:^(int code, NSString *desc) {
NSLog(@"Error fetching reactions for batch %ld: %d - %@", (long)batchIndex, code, desc);
}];
}
}
#pragma mark - TUIExtensionProtocol
- (BOOL)onRaiseExtension:(NSString *)extensionID parentView:(UIView *)parentView param:(nullable NSDictionary *)param {
if ([extensionID isEqualToString:TUICore_TUIChatExtension_ChatPopMenuReactRecentView_ClassicExtensionID]) {
TUIChatPopMenu *delegateView = [param objectForKey:TUICore_TUIChatExtension_ChatPopMenuReactRecentView_Delegate];
if (![parentView isKindOfClass:UIView.class]) {
return NO;
}
TUIReactPopRecentView * emojiRecentView = [[TUIReactPopRecentView alloc] initWithFrame:CGRectZero];
[parentView addSubview:emojiRecentView];
emojiRecentView.frame = CGRectMake(0, 0, parentView.mm_w, 44);
emojiRecentView.backgroundColor = TUIChatDynamicColor(@"chat_pop_menu_bg_color", @"#FFFFFF");
emojiRecentView.needShowbottomLine = YES;
if ([delegateView isKindOfClass:TUIChatPopMenu.class]) {
emojiRecentView.delegateView = delegateView;
}
return YES;
}
else if ([extensionID isEqualToString:TUICore_TUIChatExtension_ChatPopMenuReactDetailView_ClassicExtensionID]) {
TUIChatPopMenu *delegateView = [param objectForKey:TUICore_TUIChatExtension_ChatPopMenuReactRecentView_Delegate];
if (![parentView isKindOfClass:UIView.class]) {
return NO;
}
TUIReactPopEmojiView *emojiAdvanceView = [[TUIReactPopEmojiView alloc] initWithFrame:CGRectMake(0, 44 - 0.5, parentView.mm_w, TChatEmojiView_CollectionHeight + 10 + TChatEmojiView_Page_Height)];
[parentView addSubview:emojiAdvanceView];
[emojiAdvanceView setData:(id)[TIMConfig defaultConfig].chatPopDetailGroups];
if (delegateView) {
emojiAdvanceView.delegateView = delegateView;
}
emojiAdvanceView.alpha = 0;
emojiAdvanceView.faceCollectionView.scrollEnabled = YES;
emojiAdvanceView.faceCollectionView.delaysContentTouches = NO;
emojiAdvanceView.backgroundColor = TUIChatDynamicColor(@"chat_pop_menu_bg_color", @"#FFFFFF");
emojiAdvanceView.faceCollectionView.backgroundColor = emojiAdvanceView.backgroundColor;
return YES;
}
else if ([extensionID isEqualToString:TUICore_TUIChatExtension_ChatMessageReactPreview_ClassicExtensionID]) {
TUIMessageCell *delegateView = [param objectForKey:TUICore_TUIChatExtension_ChatMessageReactPreview_Delegate];
NSMutableDictionary *cacheMap = parentView.tui_extValueObj;
TUIReactPreview *cacheView = nil;
if (!cacheMap){
cacheMap = [NSMutableDictionary dictionaryWithCapacity:3];
}
else if ([cacheMap isKindOfClass:NSDictionary.class]) {
cacheView = [cacheMap objectForKey:NSStringFromClass(TUIReactPreview.class)];
}
else {
//cacheMap is not a dic ;
}
if (cacheView) {
[cacheView removeFromSuperview];
cacheView = nil;
}
TUIReactPreview *tagView = [[TUIReactPreview alloc] init];
[parentView addSubview:tagView];
[cacheMap setObject:tagView forKey:NSStringFromClass(TUIReactPreview.class)];
parentView.tui_extValueObj = cacheMap;
__weak typeof(self) weakSelf = self;
__weak typeof(tagView) weakTagView = tagView;
if (delegateView) {
tagView.delegateCell = (id)delegateView;
if (!delegateView.messageData.reactdataProvider) {
[delegateView.messageData setupReactDataProvider];
}
delegateView.messageData.reactValueChangedCallback = ^(NSArray<TUIReactModel *> * _Nonnull tagsArray) {
if (weakTagView) {
[weakTagView refreshByArray:(id)tagsArray];
}
};
}
if (delegateView.messageData.reactdataProvider.reactArray.count > 0) {
weakTagView.listArrM = [NSMutableArray arrayWithArray:delegateView.messageData.reactdataProvider.reactArray];
[weakTagView updateView];
if (!CGSizeEqualToSize(CGSizeZero, delegateView.messageData.messageContainerAppendSize)) {
delegateView.messageData.messageContainerAppendSize = weakTagView.frame.size;
[weakTagView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(parentView);
make.leading.mas_equalTo(parentView);
make.width.mas_equalTo(parentView);
make.height.mas_equalTo(delegateView.messageData.messageContainerAppendSize);
}];
}
else{
delegateView.messageData.messageContainerAppendSize = weakTagView.frame.size;
[weakTagView notifyReactionChanged];
}
}
else {
//When invisible, the height should be reset if there is a change in response
if (!CGSizeEqualToSize(CGSizeZero, delegateView.messageData.messageContainerAppendSize)) {
delegateView.messageData.messageContainerAppendSize = CGSizeZero;
[weakTagView notifyReactionChanged];
}
}
tagView.emojiClickCallback = ^(TUIReactModel *_Nonnull model) {
[weakSelf emojiClick:parentView ReactMessage:delegateView.messageData faceList:weakTagView.listArrM];
};
tagView.userClickCallback = ^(TUIReactModel * _Nonnull model) {
[weakSelf emojiClick:parentView ReactMessage:delegateView.messageData faceList:weakTagView.listArrM];
};
return YES;
}
//Minimalist
else if ([extensionID isEqualToString:TUICore_TUIChatExtension_ChatPopMenuReactRecentView_MinimalistExtensionID]) {
TUIChatPopContextController *delegateVC = [param objectForKey:TUICore_TUIChatExtension_ChatPopMenuReactRecentView_Delegate];
if (![parentView isKindOfClass:UIView.class]) {
return NO;
}
if (![TUIChatConfig defaultConfig].enablePopMenuEmojiReactAction) {
return NO;
}
TUIReactPopContextRecentView * emojiRecentView = [[TUIReactPopContextRecentView alloc] initWithFrame:CGRectZero];
[parentView addSubview:emojiRecentView];
emojiRecentView.frame = CGRectMake(0, 0,MAX(kTIMDefaultEmojiSize.width *8,parentView.mm_w), parentView.mm_h);
emojiRecentView.backgroundColor = [UIColor whiteColor];
emojiRecentView.needShowbottomLine = YES;
if ([delegateVC isKindOfClass:TUIChatPopContextController.class]) {
emojiRecentView.delegateVC = delegateVC;
}
return YES;
}
else if ([extensionID isEqualToString:TUICore_TUIChatExtension_ChatMessageReactPreview_MinimalistExtensionID]) {
TUIMessageCell *delegateView = [param objectForKey:TUICore_TUIChatExtension_ChatMessageReactPreview_Delegate];
NSMutableDictionary *cacheMap = parentView.tui_extValueObj;
TUIReactPreview_Minimalist *cacheView = nil;
if (!cacheMap){
cacheMap = [NSMutableDictionary dictionaryWithCapacity:3];
}
else if ([cacheMap isKindOfClass:NSDictionary.class]) {
cacheView = [cacheMap objectForKey:NSStringFromClass(TUIReactPreview_Minimalist.class)];
}
else {
//cacheMap is not a dic ;
}
if (cacheView) {
[cacheView removeFromSuperview];
cacheView = nil;
}
TUIReactPreview_Minimalist *tagView = [[TUIReactPreview_Minimalist alloc] init];
[parentView addSubview:tagView];
[cacheMap setObject:tagView forKey:NSStringFromClass(TUIReactPreview_Minimalist.class)];
parentView.tui_extValueObj = cacheMap;
__weak typeof(self) weakSelf = self;
__weak typeof(tagView) weakTagView = tagView;
if (delegateView) {
tagView.delegateCell = (id)delegateView;
if (!delegateView.messageData.reactdataProvider) {
[delegateView.messageData setupReactDataProvider];
}
delegateView.messageData.reactValueChangedCallback = ^(NSArray<TUIReactModel *> * _Nonnull tagsArray) {
if (weakTagView) {
[weakTagView refreshByArray:(id)tagsArray];
}
};
}
if (delegateView.messageData.reactdataProvider.reactArray.count > 0) {
weakTagView.reactlistArr = [NSMutableArray arrayWithArray:delegateView.messageData.reactdataProvider.reactArray];
[weakTagView updateView];
[weakTagView mas_remakeConstraints:^(MASConstraintMaker *make) {
if (delegateView.messageData.direction == MsgDirectionIncoming) {
make.leading.mas_greaterThanOrEqualTo(delegateView.container).mas_offset(kScale390(16));
} else {
make.trailing.mas_lessThanOrEqualTo(delegateView.container).mas_offset(-kScale390(16));
}
make.width.mas_equalTo(delegateView.container);
make.top.mas_equalTo(delegateView.container.mas_bottom).mas_offset(-4);
make.height.mas_equalTo(20);
}];
if (CGSizeEqualToSize(CGSizeZero, delegateView.messageData.messageContainerAppendSize)) {
if (weakTagView) {
[weakTagView refreshByArray:delegateView.messageData.reactdataProvider.reactArray];
}
}
}
else {
//When invisible, the height should be reset if there is a change in response
if (!CGSizeEqualToSize(CGSizeZero, delegateView.messageData.messageContainerAppendSize)) {
delegateView.messageData.messageContainerAppendSize = CGSizeZero;
[weakTagView notifyReactionChanged];
}
}
tagView.emojiClickCallback = ^(TUIReactModel *_Nonnull model) {
// goto detailPage
[weakSelf emojiClick:parentView ReactMessage:delegateView.messageData faceList:weakTagView.reactlistArr];
};
return YES;
}
return NO;
}
- (void)emojiClick:(UIView *)view
ReactMessage:(TUIMessageCellData *)data
faceList:(NSArray<TUIReactModel *> *)listModel {
TUIReactMembersController *detailController = [[TUIReactMembersController alloc] init];
detailController.modalPresentationStyle = UIModalPresentationCustom;
detailController.tagsArray = listModel;
detailController.originData = data;
[view.mm_viewController presentViewController:detailController animated:YES completion:nil];
}
- (void)onLoginSucceeded {
[TUIReactUtil checkCommercialAbility];
}
@end

View File

@@ -0,0 +1,21 @@
//
// TUIReactMemberCell.h
// TUIChat
//
// Created by wyl on 2022/5/26.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <UIKit/UIKit.h>
@class TUIReactMemberCellData;
NS_ASSUME_NONNULL_BEGIN
@interface TUIReactMemberCell : UITableViewCell
- (void)fillWithData:(TUIReactMemberCellData *)data;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,126 @@
//
// TUIReactMemberCell.m
// TUIChat
//
// Created by wyl on 2022/5/26.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIReactMemberCell.h"
#import <TIMCommon/NSString+TUIEmoji.h>
#import <TIMCommon/TIMCommonModel.h>
#import "TUIReactMemberCellData.h"
#define Avatar_Size 40
@interface TUIReactMemberCell ()
@property(nonatomic, strong) UIImageView *avatar;
@property(nonatomic, strong) UILabel *displayName;
@property(nonatomic, strong) UILabel *tapToRemoveLabel;
@property(nonatomic, strong) UIImageView *emoji;
@property(nonatomic, strong) TUIReactMemberCellData *data;
@end
@implementation TUIReactMemberCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
_avatar = [[UIImageView alloc] init];
_avatar.layer.cornerRadius = Avatar_Size / 2.0;
_avatar.layer.masksToBounds = YES;
[self addSubview:_avatar];
_displayName = [[UILabel alloc] init];
_displayName.font = [UIFont boldSystemFontOfSize:kScale390(16)];
_displayName.textColor = [UIColor tui_colorWithHex:@"#666666"];
_displayName.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
[self addSubview:_displayName];
_tapToRemoveLabel = [[UILabel alloc] init];
_tapToRemoveLabel.font = [UIFont systemFontOfSize:kScale390(12)];
_tapToRemoveLabel.textColor = [UIColor tui_colorWithHex:@"#666666"];
_tapToRemoveLabel.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
[self addSubview:_tapToRemoveLabel];
_emoji = [[UIImageView alloc] init];
[self addSubview:_emoji];
}
return self;
}
#pragma mark - UI
- (void)prepareUI {
self.layer.cornerRadius = 12.0f;
self.layer.masksToBounds = YES;
}
- (void)fillWithData:(TUIReactMemberCellData *)data {
self.data = data;
[_avatar sd_setImageWithURL:[NSURL URLWithString:data.faceURL] placeholderImage:DefaultAvatarImage];
_displayName.text = data.displayName;
_tapToRemoveLabel.text = @"";
if (data.isCurrentUser) {
_displayName.text = TIMCommonLocalizableString(You);
_tapToRemoveLabel.text = TIMCommonLocalizableString(TUIKitChatTap2Remove);
}
[_emoji setImage:[data.emojiName getEmojiImage]];
// tell constraints they need updating
[self setNeedsUpdateConstraints];
// update constraints now so we can animate the change
[self updateConstraintsIfNeeded];
[self layoutIfNeeded];
}
+ (BOOL)requiresConstraintBasedLayout {
return YES;
}
// this is Apple's recommended place for adding/updating constraints
- (void)updateConstraints {
[super updateConstraints];
CGFloat avatarSize = kScale390(40);
[_avatar mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(kScale390(26));
make.centerY.mas_equalTo(self.contentView);
make.width.height.mas_equalTo(avatarSize);
}];
[_displayName mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(_avatar.mas_trailing).mas_offset(kScale390(16));
make.trailing.mas_equalTo(-kScale390(60));
make.height.mas_equalTo(kScale390(22));
make.centerY.mas_equalTo(self.contentView);
}];
if (self.data.isCurrentUser) {
[_displayName mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(_avatar.mas_trailing).mas_offset(kScale390(16));
make.top.mas_equalTo(_avatar.mas_top);
make.trailing.mas_equalTo(-kScale390(60));
make.height.mas_equalTo(kScale390(22));
}];
[_tapToRemoveLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(_displayName.mas_leading);
make.top.mas_equalTo(_displayName.mas_bottom).mas_offset(kScale390(4));
make.trailing.mas_equalTo(-kScale390(60));
make.height.mas_equalTo(kScale390(12));
}];
}
[_emoji mas_remakeConstraints:^(MASConstraintMaker *make) {
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-kScale390(32));
make.centerY.mas_equalTo(self.contentView);
make.width.height.mas_equalTo(kScale390(16));
}];
}
- (void)layoutSubviews {
[super layoutSubviews];
}
@end

View File

@@ -0,0 +1,41 @@
//
// TUIReactMemberCellData.h
// TUITagDemo
//
// Created by wyl on 2022/5/12.
// Copyright © 2022 TUI. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <TIMCommon/TIMDefine.h>
#import "TUIReactModel.h"
@import ImSDK_Plus;
NS_ASSUME_NONNULL_BEGIN
@interface TUIReactMemberCellData : NSObject
@property(nonatomic, copy) NSString *emojiName;
@property(nonatomic, copy) NSString *emojiPath;
@property(nonatomic, assign) CGFloat cellHeight;
@property(nonatomic, copy) NSString *friendRemark;
@property(nonatomic, copy) NSString *nickName;
@property(nonatomic, copy) NSString *faceURL;
@property(nonatomic, copy) NSString *userID;
@property(nonatomic, assign) BOOL isCurrentUser;
- (NSString *)displayName;
@property(nonatomic, strong) TUIReactModel * tagModel;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,28 @@
//
// TUIReactMemberCellData.m
// TUITagDemo
//
// Created by wyl on 2022/5/12.
// Copyright © 2022 TUI. All rights reserved.
//
#import "TUIReactMemberCellData.h"
@implementation TUIReactMemberCellData
- (NSString *)displayName {
if (IS_NOT_EMPTY_NSSTRING(self.friendRemark)) {
return self.friendRemark;
} else if (IS_NOT_EMPTY_NSSTRING(self.nickName)) {
return self.nickName;
} else {
return self.userID;
}
return @"";
}
- (CGFloat)cellHeight {
return kScale390(72);
}
@end

View File

@@ -0,0 +1,36 @@
//
// TUIReactMembersController.h
// TUIChat
//
// Created by wyl on 2022/10/31.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <TIMCommon/TUIMessageCellData.h>
#import <TUIChat/TUIChatFlexViewController.h>
#import "TUIReactModel.h"
#import "TUIReactMemberCellData.h"
NS_ASSUME_NONNULL_BEGIN
@interface TUIChatMembersReactSubController : UIViewController <UITableViewDelegate, UITableViewDataSource>
@property(nonatomic, strong) UITableView *tableView;
@property(nonatomic, strong) NSMutableArray<TUIReactMemberCellData *> *data;
@property(nonatomic, strong) TUIMessageCellData *originData;
@property(nonatomic, copy) void (^emojiClickCallback)(TUIReactModel *model);
@end
@interface TUIReactMembersController : TUIChatFlexViewController
@property(nonatomic, strong) TUIMessageCellData *originData;
@property(nonatomic, strong) NSArray<TUIReactModel *> *tagsArray;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,229 @@
//
// TUIReactMembersController.m
// TUIChat
//
// Created by wyl on 2022/10/31.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIReactMembersController.h"
#import <TIMCommon/TUIMessageCell.h>
#import <TUICore/TUILogin.h>
#import "TUIReactMembersSegementScrollView.h"
#import "TUIReactMemberCell.h"
#import "TUIReactMemberCellData.h"
#import "TUIEmojiReactDataProvider.h"
@implementation TUIChatMembersReactSubController
- (instancetype)init {
self = [super init];
if (self) {
[self configTableView];
}
return self;
}
- (void)configTableView {
self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
[self.view addSubview:self.tableView];
self.tableView.frame = self.view.bounds;
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.tableView setSeparatorInset:UIEdgeInsetsMake(0, 0, 0, 0)];
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
[self.tableView registerClass:[TUIReactMemberCell class] forCellReuseIdentifier:@"TUIReactMemberCell"];
if (@available(iOS 15.0, *)) {
self.tableView.sectionHeaderTopPadding = 0;
}
@weakify(self);
[RACObserve(self.view, frame) subscribeNext:^(id _Nullable frame) {
@strongify(self);
self.tableView.frame = self.view.bounds;
}];
}
- (NSMutableArray *)data {
if (!_data) {
_data = [NSMutableArray arrayWithCapacity:3];
}
return _data;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
{
TUIReactMemberCellData *data = self.data[indexPath.section];
return data.cellHeight;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
{ return 1; }
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.data.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TUIReactMemberCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TUIReactMemberCell"];
if (cell == nil) {
cell = [[TUIReactMemberCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"TUIReactMemberCell"];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell fillWithData:self.data[indexPath.row]];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
TUIReactMemberCellData *data = self.data[indexPath.row];
if (data.isCurrentUser) {
if (self.emojiClickCallback) {
self.emojiClickCallback(data.tagModel);
}
}
}
@end
@interface TUIReactMembersController () <V2TIMAdvancedMsgListener>
@property(nonatomic, strong) TUIReactMembersSegementScrollView *scrollview;
@property(nonatomic, strong) TUIEmojiReactDataProvider *provider;
@property(nonatomic, strong) NSMutableArray *tabItems;
@property(nonatomic, strong) NSMutableArray *tabPageVCArray;
@end
@implementation TUIReactMembersController
- (void)viewDidLoad {
[super viewDidLoad];
[self setNormalBottom];
self.provider = [[TUIEmojiReactDataProvider alloc] init];
[self loadList];
[self registerTUIKitNotification];
self.containerView.backgroundColor = TIMCommonDynamicColor(@"form_bg_color", @"#FFFFFF");
[self dealData];
[self reloadData];
}
- (void)updateSubContainerView {
[super updateSubContainerView];
self.scrollview.frame = CGRectMake(0, self.topGestureView.frame.size.height, self.containerView.frame.size.width,
self.containerView.frame.size.height - self.topGestureView.frame.size.height);
[self.scrollview updateContainerView];
}
- (void)loadList {
if (!self.provider) {
self.provider = [[TUIEmojiReactDataProvider alloc] init];
}
self.provider.msgId = self.originData.innerMessage.msgID;
__weak typeof(self)weakSelf = self;
[self.provider getMessageReactions:@[self.originData.innerMessage] maxUserCountPerReaction:5 succ:^(NSArray<TUIReactModel *> * _Nonnull tagsArray, NSMutableDictionary * _Nonnull tagsMap) {
weakSelf.tagsArray = tagsArray;
[weakSelf dealData];
[weakSelf reloadData];
} fail:^(int code, NSString *desc) {
}];
self.provider.changed = ^(NSArray<TUIReactModel *> * _Nonnull tagsArray, NSMutableDictionary * _Nonnull tagsMap) {
weakSelf.tagsArray = tagsArray;
[weakSelf dealData];
[weakSelf reloadData];
if (tagsArray.count <= 0) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf dismissViewControllerAnimated:YES completion:nil];
});
}
};
}
- (void)dealData {
NSMutableArray *tabItems = [NSMutableArray arrayWithCapacity:3];
self.tabItems = tabItems;
int summaryCount = 0;
// config tabItems
for (TUIReactModel *tagsModel in _tagsArray) {
TUIReactMembersSegementItem *item = [[TUIReactMembersSegementItem alloc] init];
item.title = [NSString stringWithFormat:@"%ld", tagsModel.followUserModels.count];
item.facePath = tagsModel.emojiPath;
[tabItems addObject:item];
summaryCount += tagsModel.followUserModels.count;
}
// config cellData
NSMutableArray *pageTabCellDatasArray = [NSMutableArray arrayWithCapacity:3];
for (TUIReactModel *tagsModel in _tagsArray) {
NSMutableArray<TUIReactMemberCellData *> *emojiCellDatas = [NSMutableArray arrayWithCapacity:3];
// config each tag
NSMutableArray<TUIReactUserModel *> *followUserModels = tagsModel.followUserModels;
for (TUIReactUserModel *userModel in followUserModels) {
TUIReactMemberCellData *cellData = [[TUIReactMemberCellData alloc] init];
cellData.tagModel = tagsModel;
cellData.emojiName = tagsModel.emojiKey;
cellData.emojiPath = tagsModel.emojiPath;
cellData.faceURL = userModel.faceURL;
cellData.friendRemark = userModel.friendRemark;
cellData.nickName = userModel.nickName;
cellData.userID = userModel.userID;
NSString *userID = userModel.userID;
if ([userID isEqualToString:[TUILogin getUserID]]) {
cellData.isCurrentUser = YES;
}
[emojiCellDatas addObject:cellData];
}
[pageTabCellDatasArray addObject:emojiCellDatas];
}
// config subViews
NSMutableArray *tabPageVCArray = [NSMutableArray arrayWithCapacity:3];
self.tabPageVCArray = tabPageVCArray;
for (int i = 0; i < tabItems.count; i++) {
TUIChatMembersReactSubController *subController = [[TUIChatMembersReactSubController alloc] init];
subController.data = pageTabCellDatasArray[i];
subController.originData = self.originData;
__weak typeof(self)weakSelf = self;
subController.emojiClickCallback = ^(TUIReactModel * _Nonnull model) {
[weakSelf.provider removeMessageReaction:weakSelf.originData.innerMessage reactionID:model.emojiKey succ:^{
} fail:^(int code, NSString *desc) {
}];
};
[tabPageVCArray addObject:subController];
}
}
- (void)reloadData {
if (self.scrollview) {
[self.scrollview removeFromSuperview];
}
self.scrollview =
[[TUIReactMembersSegementScrollView alloc] initWithFrame:CGRectMake(0, self.topGestureView.frame.size.height, self.containerView.frame.size.width,
self.containerView.frame.size.height - self.topGestureView.frame.size.height)
SegementItems:self.tabItems
viewArray:self.tabPageVCArray];
[self.containerView addSubview:self.scrollview];
}
- (void)registerTUIKitNotification {
[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];
}
@end

View File

@@ -0,0 +1,63 @@
//
// TUIReactMembersSegementScrollView.h
// TUIChat
//
// Created by wyl on 2022/10/31.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <UIKit/UIKit.h>
@class TUIReactMembersSegementItem;
NS_ASSUME_NONNULL_BEGIN
typedef void (^btnClickedBlock)(int index);
@interface TUIReactMembersSegementItem : NSObject
@property(nonatomic, copy) NSString *title;
@property(nonatomic, copy) NSString *facePath;
@end
@interface TUIReactMembersSegementButtonView : UIView
@property (nonatomic,strong) UIView *contentView;
@property (nonatomic,strong) UIImageView * img;
@property (nonatomic,strong) UILabel * title;
@end
@interface TUIReactMembersSegementView : UIView <UIScrollViewDelegate> {
NSInteger nPageIndex;
NSInteger titleCount;
TUIReactMembersSegementButtonView *currentBtn;
NSMutableArray *btnArray;
}
- (void)setPageIndex:(int)nIndex;
- (instancetype)initWithFrame:(CGRect)frame SegementItems:(NSArray<TUIReactMembersSegementItem *> *)items block:(btnClickedBlock)clickedBlock;
@property(nonatomic, copy) btnClickedBlock block;
@property(strong, nonatomic) UIScrollView *segementScrollView;
@property(strong, nonatomic) UIView *selectedLine;
@end
@interface TUIReactMembersSegementScrollView : UIView
- (instancetype)initWithFrame:(CGRect)frame SegementItems:(NSArray<TUIReactMembersSegementItem *> *)items viewArray:(NSArray *)viewArray;
// Top Tab Button Scroll View (segementView)
@property(strong, nonatomic) TUIReactMembersSegementView *mySegementView;
@property(strong, nonatomic) UIScrollView *pageScrollView;
- (void)updateContainerView;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,295 @@
//
// TUIReactMembersSegementScrollView.m
// TUIChat
//
// Created by wyl on 2022/10/31.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIReactMembersSegementScrollView.h"
#import <TIMCommon/TIMDefine.h>
#import <TIMCommon/TUIFitButton.h>
#import <TUICore/TUIThemeManager.h>
#define SEGEMENT_BTN_WIDTH kScale390(43)
#define SEGEMENT_BTN_HEIGHT kScale390(20)
@implementation TUIReactMembersSegementItem
@end
@implementation TUIReactMembersSegementButtonView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setupView];
}
return self;
}
- (void)setupView {
[self addSubview:self.contentView];
[self.contentView addSubview:self.img];
[self.contentView addSubview:self.title];
}
- (UIView *)contentView {
if (!_contentView) {
_contentView = [[UIView alloc] init];
}
return _contentView;
}
- (UIImageView *)img {
if (!_img) {
_img = [[UIImageView alloc] init];
}
return _img;
}
- (UILabel *)title {
if (!_title) {
_title = [[UILabel alloc] init];
_title.font = [UIFont systemFontOfSize:kScale390(12)];
_title.tintColor = [UIColor tui_colorWithHex:@"#141516"];
}
return _title;
}
- (void)layoutSubviews {
[super layoutSubviews];
[self.contentView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(self.img);
make.center.mas_equalTo(self);
make.trailing.mas_equalTo(self.title);
make.height.mas_equalTo(self);
}];
[self.img mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(self.contentView).mas_offset(kScale390(3));
make.centerY.mas_equalTo(self);
make.width.height.mas_equalTo(kScale390(12));
}];
[self.title sizeToFit];
[self.title mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(self.img.mas_trailing).mas_offset(kScale390(5));
make.centerY.mas_equalTo(self);
make.width.mas_equalTo(self.title.frame.size.width);
make.height.mas_equalTo(self.title.font.lineHeight);
}];
}
@end
@implementation TUIReactMembersSegementView
- (instancetype)initWithFrame:(CGRect)frame SegementItems:(NSArray<TUIReactMembersSegementItem *> *)items block:(btnClickedBlock)clickedBlock {
self = [super initWithFrame:frame];
if (self) {
[self addSubview:self.segementScrollView];
self.block = clickedBlock;
nPageIndex = 1;
titleCount = items.count;
btnArray = [NSMutableArray array];
CGFloat padding = kScale390(8);
TUIReactMembersSegementButtonView *preBtn = nil;
for (int i = 0; i < titleCount; i++) {
TUIReactMembersSegementButtonView *btn = nil;
if (preBtn == nil) {
btn = [[TUIReactMembersSegementButtonView alloc] initWithFrame:CGRectMake(0,
(self.frame.size.height - SEGEMENT_BTN_HEIGHT) * 0.5, SEGEMENT_BTN_WIDTH, SEGEMENT_BTN_HEIGHT)];
currentBtn = btn;
}
else {
btn = [[TUIReactMembersSegementButtonView alloc] initWithFrame:CGRectMake(preBtn.frame.origin.x + preBtn.frame.size.width + padding,
(self.frame.size.height - SEGEMENT_BTN_HEIGHT) * 0.5, SEGEMENT_BTN_WIDTH, SEGEMENT_BTN_HEIGHT)];
}
preBtn = btn;
btn.title.text = items[i].title;
btn.img.image = [[TUIImageCache sharedInstance] getFaceFromCache:items[i].facePath];
btn.tag = i + 1;
UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(btnClick:)];
[btn addGestureRecognizer:tapGesture];
[self.segementScrollView addSubview:btn];
[btnArray addObject:btn];
}
self.selectedLine.frame =
CGRectMake(currentBtn.frame.origin.x, (self.frame.size.height - SEGEMENT_BTN_HEIGHT) * 0.5, MAX(currentBtn.frame.size.width, SEGEMENT_BTN_WIDTH), SEGEMENT_BTN_HEIGHT);
self.selectedLine.layer.cornerRadius = kScale390(10);
[self.segementScrollView addSubview:self.selectedLine];
self.segementScrollView.contentSize = CGSizeMake(titleCount * SEGEMENT_BTN_WIDTH + (padding * titleCount - 1), 0);
if (isRTL()) {
self.segementScrollView.transform = CGAffineTransformMakeRotation(M_PI);
NSArray *subViews = self.segementScrollView.subviews;
for (UIView *subView in subViews) {
subView.transform = CGAffineTransformMakeRotation(M_PI);
}
}
}
return self;
}
- (UIScrollView *)segementScrollView {
if (_segementScrollView == nil) {
CGRect rect = self.bounds;
_segementScrollView = [[UIScrollView alloc] initWithFrame:rect];
_segementScrollView.showsHorizontalScrollIndicator = NO;
_segementScrollView.showsVerticalScrollIndicator = NO;
_segementScrollView.bounces = NO;
_segementScrollView.pagingEnabled = NO;
_segementScrollView.delegate = self;
_segementScrollView.scrollsToTop = NO;
}
return _segementScrollView;
}
- (UIView *)selectedLine {
if (_selectedLine == nil) {
_selectedLine = [[UIView alloc] init];
_selectedLine.backgroundColor = [[UIColor tui_colorWithHex:@"#F9F9F9"] colorWithAlphaComponent:0.54];
}
return _selectedLine;
}
- (void)setPageIndex:(int)nIndex {
if (nIndex != nPageIndex) {
nPageIndex = nIndex;
[self refreshSegement];
}
}
- (void)refreshSegement {
for (TUIReactMembersSegementButtonView *btn in btnArray) {
if (btn.tag == nPageIndex) {
currentBtn = btn;
}
}
if (currentBtn.frame.origin.x + SEGEMENT_BTN_WIDTH > self.frame.size.width + self.segementScrollView.contentOffset.x) {
[self.segementScrollView setContentOffset:CGPointMake(self.segementScrollView.contentOffset.x + SEGEMENT_BTN_WIDTH, 0) animated:YES];
}
else if (currentBtn.frame.origin.x < self.segementScrollView.contentOffset.x) {
[self.segementScrollView setContentOffset:CGPointMake(currentBtn.frame.origin.x, 0) animated:YES];
}
[UIView animateWithDuration:0.2
animations:^{
self.selectedLine.frame =
CGRectMake(currentBtn.frame.origin.x, (self.frame.size.height - SEGEMENT_BTN_HEIGHT) * 0.5, currentBtn.frame.size.width, SEGEMENT_BTN_HEIGHT);
self.selectedLine.layer.cornerRadius = kScale390(10);
}
completion:^(BOOL finished){
}];
}
- (void)btnClick:(UIGestureRecognizer *)recognizer {
TUIReactMembersSegementButtonView *btn = (id)recognizer.view;
currentBtn = btn;
if (nPageIndex != btn.tag) {
[self showHapticFeedback];
nPageIndex = btn.tag;
[self refreshSegement];
self.block(nPageIndex);
}
}
- (void)showHapticFeedback {
if (@available(iOS 10.0, *)) {
dispatch_async(dispatch_get_main_queue(), ^{
UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight];
[generator prepare];
[generator impactOccurred];
});
} else {
// Fallback on earlier versions
}
}
@end
#define SCROLLVIEW_WIDTH [UIScreen mainScreen].bounds.size.width
#define SCROLLVIEW_HEIGTH self.bounds.size.height
#define SEGEMENT_HEIGTHT 22
@interface TUIReactMembersSegementScrollView () <UIScrollViewDelegate>
@property(nonatomic, strong) NSArray<TUIReactMembersSegementItem *> *items;
@property(nonatomic, strong) NSArray *viewArray;
@end
@implementation TUIReactMembersSegementScrollView
- (instancetype)initWithFrame:(CGRect)frame SegementItems:(NSArray<TUIReactMembersSegementItem *> *)items viewArray:(NSArray *)viewArray {
self = [super initWithFrame:frame];
self.items = items;
self.viewArray = viewArray;
[self addSubview:self.mySegementView];
[self addSubview:self.pageScrollView];
if (self) {
for (int i = 0; i < viewArray.count; i++) {
UIViewController *viewController = viewArray[i];
viewController.view.frame = CGRectMake(i * SCROLLVIEW_WIDTH, 0, SCROLLVIEW_WIDTH, self.pageScrollView.frame.size.height);
[self.pageScrollView addSubview:viewController.view];
}
self.pageScrollView.contentSize = CGSizeMake(viewArray.count * SCROLLVIEW_WIDTH, self.pageScrollView.frame.size.height);
if (isRTL()) {
_pageScrollView.transform = CGAffineTransformMakeRotation(M_PI);
NSArray *subViews = _pageScrollView.subviews;
for (UIView *subView in subViews) {
subView.transform = CGAffineTransformMakeRotation(M_PI);
}
}
}
return self;
}
- (void)updateContainerView {
self.pageScrollView.frame = CGRectMake(0, _mySegementView.frame.size.height, SCROLLVIEW_WIDTH, self.bounds.size.height - _mySegementView.frame.size.height);
for (int i = 0; i < self.viewArray.count; i++) {
UIViewController *viewController = self.viewArray[i];
viewController.view.frame = CGRectMake(i * SCROLLVIEW_WIDTH, 0, SCROLLVIEW_WIDTH, self.pageScrollView.frame.size.height);
}
self.pageScrollView.contentSize = CGSizeMake(self.viewArray.count * SCROLLVIEW_WIDTH, self.pageScrollView.frame.size.height);
}
- (TUIReactMembersSegementView *)mySegementView {
if (_mySegementView == nil) {
_mySegementView = [[TUIReactMembersSegementView alloc] initWithFrame:CGRectMake(kScale390(27), 0, SCROLLVIEW_WIDTH - kScale390(54), SEGEMENT_HEIGTHT)
SegementItems:_items
block:^(int index) {
[_pageScrollView setContentOffset:CGPointMake((index - 1) * SCROLLVIEW_WIDTH, 0)];
}];
}
return _mySegementView;
}
- (UIScrollView *)pageScrollView {
if (_pageScrollView == nil) {
_pageScrollView = [[UIScrollView alloc]
initWithFrame:CGRectMake(0, _mySegementView.frame.size.height, SCROLLVIEW_WIDTH, SCROLLVIEW_HEIGTH - _mySegementView.frame.size.height)];
_pageScrollView.backgroundColor = [UIColor clearColor];
_pageScrollView.delegate = self;
_pageScrollView.showsVerticalScrollIndicator = NO;
_pageScrollView.showsHorizontalScrollIndicator = NO;
_pageScrollView.bounces = NO;
_pageScrollView.scrollsToTop = NO;
_pageScrollView.pagingEnabled = YES;
}
return _pageScrollView;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (scrollView == _pageScrollView) {
int p = _pageScrollView.contentOffset.x / SCROLLVIEW_WIDTH;
[_mySegementView setPageIndex:p + 1];
}
}
@end

View File

@@ -0,0 +1,23 @@
//
// TUIReactContextEmojiDetailController.h
// TUIChat
//
// Created by wyl on 2022/10/27.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIChatFlexViewController.h"
#import <TUIChat/TUIFaceVerticalView.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIReactContextPopEmojiFaceView : TUIFaceVerticalView
@end
@interface TUIReactContextEmojiDetailController : TUIChatFlexViewController
@property(nonatomic, copy) void (^reactClickCallback)(NSString *faceName);
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,121 @@
//
// TUIReactContextEmojiDetailController.m
// TUIChat
//
// Created by wyl on 2022/10/27.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIReactContextEmojiDetailController.h"
#import <TIMCommon/TIMCommonMediator.h>
#import <TIMCommon/TUIEmojiMeditorProtocol.h>
#import <TUIChat/TUIChatConfig.h>
#import <TIMCommon/NSString+TUIEmoji.h>
@implementation TUIReactContextPopEmojiFaceView
- (void)setData:(NSMutableArray *)data {
[super setData:data];
self.floatCtrlView.hidden = YES;
self.backgroundColor = [UIColor clearColor];
self.faceCollectionView.backgroundColor = self.backgroundColor;
}
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
referenceSizeForHeaderInSection:(NSInteger)section {
return CGSizeMake(self.frame.size.width, 0);
}
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
int groupIndex = [self.groupIndexInSection[indexPath.section] intValue];
TUIFaceGroup *group = self.faceGroups[groupIndex];
CGFloat width = (self.frame.size.width - TFaceView_Page_Padding * 2 - TFaceView_Margin * (group.itemCountPerRow - 1)) / group.itemCountPerRow;
CGFloat height = width ;
return CGSizeMake(width, height);
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
TUIFaceCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:TFaceCell_ReuseId forIndexPath:indexPath];
int groupIndex = [self.groupIndexInSection[indexPath.section] intValue];
TUIFaceGroup *group = self.faceGroups[groupIndex];
int itemCount = group.faces.count;
NSNumber *index = [self.itemIndexs objectForKey:indexPath];
if (index.integerValue < group.faces.count) {
TUIFaceCellData *data = group.faces[index.integerValue];
[cell setData:data];
} else {
[cell setData:nil];
}
cell.face.contentMode = UIViewContentModeScaleAspectFill;
return cell;
}
@end
@interface TUIReactContextEmojiDetailController () <TUIFaceVerticalViewDelegate>
@property(nonatomic, strong) TUIReactContextPopEmojiFaceView *faceView;
@end
@implementation TUIReactContextEmojiDetailController
- (void)viewDidLoad {
[super viewDidLoad];
[self setNormalBottom];
self.containerView.backgroundColor = [UIColor whiteColor];
[self.containerView addSubview:self.faceView];
}
- (TUIReactContextPopEmojiFaceView *)faceView {
if (!_faceView) {
_faceView =
[[TUIReactContextPopEmojiFaceView alloc] initWithFrame:CGRectMake(0, self.topGestureView.frame.size.height, self.containerView.frame.size.width,
self.containerView.frame.size.height - self.topGestureView.frame.size.height)];
_faceView.delegate = self;
[_faceView setData:(id)[TUIChatConfig defaultConfig].chatContextEmojiDetailGroups];
_faceView.backgroundColor = [UIColor clearColor];
}
return _faceView;
}
- (void)updateSubContainerView {
[super updateSubContainerView];
_faceView.frame = CGRectMake(0, self.topGestureView.frame.size.height, self.containerView.frame.size.width,
self.containerView.frame.size.height - self.topGestureView.frame.size.height);
}
- (void)updateRecentMenuQueue:(NSString *)faceName {
id<TUIEmojiMeditorProtocol> service = [[TIMCommonMediator share] getObject:@protocol(TUIEmojiMeditorProtocol)];
return [service updateRecentMenuQueue:faceName];
}
- (void)faceView:(TUIFaceView *)faceView scrollToFaceGroupIndex:(NSInteger)index {
}
- (void)faceView:(TUIFaceView *)faceView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
TUIFaceGroup *group = faceView.faceGroups[indexPath.section];
TUIFaceCellData *face = group.faces[indexPath.row];
if (indexPath.section == 0) {
NSString *faceName = face.name;
NSLog(@"%@", faceName);
[self updateRecentMenuQueue:faceName];
[self dismissViewControllerAnimated:NO completion:nil];
if (self.reactClickCallback) {
self.reactClickCallback(faceName);
}
}
}
- (void)faceViewDidBackDelete:(TUIFaceView *)faceView {
}
@end

View File

@@ -0,0 +1,23 @@
//
// TUIReactPopContextRecentView.h
// TUIChat
//
// Created by wyl on 2022/10/24.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <TUIChat/TUIChatPopContextController.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIReactPopContextRecentView : UIView
@property(nonatomic, strong, readonly) NSMutableArray *faceGroups;
@property(nonatomic, assign) BOOL needShowbottomLine;
@property(nonatomic, strong) UIButton *arrowButton;
@property(nonatomic, weak) TUIChatPopContextController* delegateVC;
- (void)setData:(NSMutableArray *)data;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,203 @@
//
// TUIReactPopContextRecentView.m
// TUIChat
//
// Created by wyl on 2022/10/24.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIReactPopContextRecentView.h"
#import <TIMCommon/NSString+TUIEmoji.h>
#import <TIMCommon/TIMCommonModel.h>
#import <TIMCommon/TIMDefine.h>
#import <TIMCommon/TUIFitButton.h>
#import <TIMCommon/TIMCommonMediator.h>
#import <TIMCommon/TUIEmojiMeditorProtocol.h>
#import "TUIReactContextEmojiDetailController.h"
#import "TUIMessageCellData+Reaction.h"
#define kTIMRecentDefaultEmojiSize CGSizeMake(25, 25)
@interface TUIReactPopContextRecentView ()
@property(nonatomic, strong) NSMutableArray *sectionIndexInGroup;
@property(nonatomic, strong) NSMutableArray *pageCountInGroup;
@property(nonatomic, strong) NSMutableArray *groupIndexInSection;
@property(nonatomic, strong) NSMutableDictionary *itemIndexs;
@property(nonatomic, assign) NSInteger sectionCount;
@end
@implementation TUIReactPopContextRecentView
- (void)layoutSubviews {
[super layoutSubviews];
[self defaultLayout];
}
- (void)defaultLayout {
[self setupCorner];
[self setupDefaultArray];
}
- (void)setupCorner {
UIRectCorner corner = UIRectCornerAllCorners;
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corner cornerRadii:CGSizeMake(kScale390(22), kScale390(22))];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
}
- (void)setupDefaultArray {
NSMutableArray *faceArray = [NSMutableArray array];
TUIFaceGroup *defaultFaceGroup = [self findFaceGroupAboutType];
if (defaultFaceGroup) {
[faceArray addObject:defaultFaceGroup];
}
[self setData:faceArray];
}
- (void)setData:(NSMutableArray *)data {
_faceGroups = data;
_sectionIndexInGroup = [NSMutableArray array];
_groupIndexInSection = [NSMutableArray array];
_itemIndexs = [NSMutableDictionary dictionary];
NSInteger sectionIndex = 0;
for (NSInteger groupIndex = 0; groupIndex < _faceGroups.count; ++groupIndex) {
TUIFaceGroup *group = _faceGroups[groupIndex];
[_sectionIndexInGroup addObject:@(sectionIndex)];
int itemCount = group.rowCount * group.itemCountPerRow;
int sectionCount = ceil(group.faces.count * 1.0 / (itemCount - 0));
for (int sectionIndex = 0; sectionIndex < sectionCount; ++sectionIndex) {
[_groupIndexInSection addObject:@(groupIndex)];
}
sectionIndex += sectionCount;
}
_sectionCount = sectionIndex;
for (NSInteger curSection = 0; curSection < _sectionCount; ++curSection) {
NSNumber *groupIndex = _groupIndexInSection[curSection];
NSNumber *groupSectionIndex = _sectionIndexInGroup[groupIndex.integerValue];
TUIFaceGroup *face = _faceGroups[groupIndex.integerValue];
NSInteger itemCount = face.rowCount * face.itemCountPerRow;
NSInteger groupSection = curSection - groupSectionIndex.integerValue;
for (NSInteger itemIndex = 0; itemIndex < itemCount; ++itemIndex) {
// transpose line/row
NSInteger row = itemIndex % face.rowCount;
NSInteger column = itemIndex / face.rowCount;
NSInteger reIndex = face.itemCountPerRow * row + column + groupSection * itemCount;
[_itemIndexs setObject:@(reIndex) forKey:[NSIndexPath indexPathForRow:itemIndex inSection:curSection]];
}
}
[self createBtns];
if (self.needShowbottomLine) {
float margin = 20;
UIView *line = [[UIView alloc] initWithFrame:CGRectMake(margin, self.frame.size.height - 1, self.frame.size.width - 2 * margin, 0.5)];
;
[self addSubview:line];
line.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB");
}
}
- (TUIFaceGroup *)findFaceGroupAboutType {
// emoji group
id<TUIEmojiMeditorProtocol> service = [[TIMCommonMediator share] getObject:@protocol(TUIEmojiMeditorProtocol)];
TUIFaceGroup * group = [service getChatPopMenuRecentQueue];
group.faces = [NSMutableArray arrayWithArray:[group.faces subarrayWithRange:NSMakeRange(0, 6)]];
return group;
}
- (void)createBtns {
if (self.subviews) {
for (UIView *subView in self.subviews) {
[subView removeFromSuperview];
}
}
int groupIndex = [_groupIndexInSection[0] intValue];
TUIFaceGroup *group = _faceGroups[groupIndex];
int tag = 0;
float margin = kScale390(8);
float padding = kScale390(2);
UIButton *preBtn = nil;
for (TUIFaceCellData *cellData in group.faces) {
UIButton *button = [self buttonWithCellImage:[[TUIImageCache sharedInstance] getFaceFromCache:cellData.path] Tag:tag];
[self addSubview:button];
if (tag == 0) {
button.mm_width(kTIMRecentDefaultEmojiSize.width).mm_height( kTIMRecentDefaultEmojiSize.height).mm_left(margin).mm_top(kScale390(8));
} else {
button.mm_width(kTIMRecentDefaultEmojiSize.width).mm_height( kTIMRecentDefaultEmojiSize.height).mm_left(preBtn.mm_x + preBtn.mm_w + padding).mm_top(kScale390(8));
}
tag++;
preBtn = button;
}
self.arrowButton = [self buttonWithCellImage:[UIImage imageNamed:TUIChatImagePath_Minimalist(@"icon_emoji_more")] Tag:999];
[self addSubview:self.arrowButton];
self.arrowButton.mm_width(kScale390(24)).mm_height(kScale390(24)).mm_right(margin).mm_top(kScale390(8));
if (isRTL()) {
for (UIView *subview in self.subviews) {
[subview resetFrameToFitRTL];
}
}
}
- (UIButton *)buttonWithCellImage:(UIImage *)img Tag:(NSInteger)tag {
TUIFitButton *actionButton = [TUIFitButton buttonWithType:UIButtonTypeCustom];
actionButton.imageSize = CGSizeMake(kTIMRecentDefaultEmojiSize.width,kTIMRecentDefaultEmojiSize.height);
[actionButton setImage:img forState:UIControlStateNormal];
actionButton.contentMode = UIViewContentModeScaleAspectFit;
[actionButton addTarget:self action:@selector(onClick:) forControlEvents:UIControlEventTouchUpInside];
actionButton.tag = tag;
return actionButton;
}
- (void)onClick:(UIButton *)btn {
if (btn.tag == 999) {
btn.selected = !btn.selected;
[self showDetailPage];
} else {
TUIFaceGroup *group = self.faceGroups[0];
TUIFaceCellData *face = group.faces[btn.tag];
NSString *faceName = face.name;
[self updateReactClick:faceName];
if (_delegateVC) {
[_delegateVC blurDismissViewControllerAnimated:YES completion:^(BOOL finished) {
}];
}
}
}
- (void)showDetailPage {
TUIReactContextEmojiDetailController *detailController = [[TUIReactContextEmojiDetailController alloc] init];
detailController.modalPresentationStyle = UIModalPresentationCustom;
__weak typeof(self) weakSelf = self;
detailController.reactClickCallback = ^(NSString *_Nonnull faceName) {
__strong typeof(weakSelf) strongSelf = weakSelf;
[weakSelf updateReactClick:faceName];
if (weakSelf.delegateVC) {
[weakSelf.delegateVC blurDismissViewControllerAnimated:YES completion:^(BOOL finished) {
}];
}
};
[self.mm_viewController presentViewController:detailController animated:YES completion:nil];
}
- (void)hideDetailPage {
if (self.delegateVC) {
[self.delegateVC blurDismissViewControllerAnimated:YES completion:^(BOOL finished) {
}];
}
}
- (void)updateReactClick:(NSString *)faceName {
if (self.delegateVC.alertViewCellData) {
[self.delegateVC.alertViewCellData updateReactClick:faceName];
}
}
@end

View File

@@ -0,0 +1,20 @@
//
// TUIReactPopEmojiView.h
// TUIChat
//
// Created by wyl on 2022/4/20.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <TUIChat/TUIFaceView.h>
#import <TUIChat/TUIChatPopMenuDefine.h>
#import <TUIChat/TUIChatPopMenu.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIReactPopEmojiView : TUIFaceView
@property(nonatomic, weak) TUIChatPopMenu *delegateView;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,127 @@
//
// TUIReactPopEmojiView.m
// TUIChat
//
// Created by wyl on 2022/4/20.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIReactPopEmojiView.h"
#import <TIMCommon/NSString+TUIEmoji.h>
#import <TIMCommon/TIMCommonModel.h>
#import <TIMCommon/TIMDefine.h>
#import <TUIChat/TUIChatPopMenu.h>
#import <TIMCommon/TIMCommonMediator.h>
#import <TIMCommon/TUIEmojiMeditorProtocol.h>
#import "TUIMessageCellData+Reaction.h"
@interface TUIReactPopEmojiView ()
@end
@implementation TUIReactPopEmojiView
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
int groupIndex = [self.groupIndexInSection[indexPath.section] intValue];
TUIFaceGroup *group = self.faceGroups[groupIndex];
CGFloat width = (self.frame.size.width - TChatEmojiView_Padding * 2 - TChatEmojiView_Margin * (group.itemCountPerRow - 1)) / group.itemCountPerRow;
CGFloat height = (collectionView.frame.size.height - TChatEmojiView_MarginTopBottom * (group.rowCount - 1)) / group.rowCount;
return CGSizeMake(width, height);
}
- (void)layoutSubviews {
[super layoutSubviews];
[self defaultLayout];
[self updateFrame];
[self updateCorner];
}
- (void)defaultLayout {
self.faceFlowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.faceFlowLayout.minimumLineSpacing = TChatEmojiView_Margin;
self.faceFlowLayout.minimumInteritemSpacing = TChatEmojiView_MarginTopBottom;
self.faceFlowLayout.sectionInset = UIEdgeInsetsMake(0, TChatEmojiView_Padding, 0, TChatEmojiView_Padding);
self.faceCollectionView.collectionViewLayout = self.faceFlowLayout;
}
- (void)updateFrame {
self.faceCollectionView.frame = CGRectMake(0, TChatEmojiView_CollectionOffsetY, self.frame.size.width, TChatEmojiView_CollectionHeight);
self.pageControl.frame =
CGRectMake(0, TChatEmojiView_CollectionOffsetY + self.faceCollectionView.frame.size.height, self.frame.size.width, TChatEmojiView_Page_Height);
}
- (void)updateCorner {
UIRectCorner corner = UIRectCornerBottomLeft | UIRectCornerBottomRight;
CGRect bounds = CGRectMake(self.bounds.origin.x, self.bounds.origin.y - 1, self.bounds.size.width, self.bounds.size.height);
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:bounds byRoundingCorners:corner cornerRadii:CGSizeMake(5, 5)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
}
- (void)setData:(NSMutableArray *)data {
[super setData:data];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
TUIFaceCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:TFaceCell_ReuseId forIndexPath:indexPath];
cell.face.contentMode = UIViewContentModeScaleAspectFill;
int groupIndex = [self.groupIndexInSection[indexPath.section] intValue];
TUIFaceGroup *group = self.faceGroups[groupIndex];
int itemCount = group.rowCount * group.itemCountPerRow;
if (indexPath.row == itemCount - 1 && group.needBackDelete) {
TUIFaceCellData *data = [[TUIFaceCellData alloc] init];
data.path = TUIChatFaceImagePath(@"del_normal");
[cell setData:data];
cell.face.image = [cell.face.image rtl_imageFlippedForRightToLeftLayoutDirection];
} else {
NSNumber *index = [self.itemIndexs objectForKey:indexPath];
if (index.integerValue < group.faces.count) {
TUIFaceCellData *data = group.faces[index.integerValue];
[cell setData:data];
} else {
[cell setData:nil];
}
}
cell.frame = CGRectMake(cell.frame.origin.x, cell.frame.origin.y, kTIMDefaultEmojiSize.width, kTIMDefaultEmojiSize.height);
cell.face.frame = CGRectMake(cell.face.frame.origin.x, cell.face.frame.origin.y, kTIMDefaultEmojiSize.width, kTIMDefaultEmojiSize.height);;
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
int groupIndex = [self.groupIndexInSection[indexPath.section] intValue];
TUIFaceGroup *faces = self.faceGroups[groupIndex];
int itemCount = faces.rowCount * faces.itemCountPerRow;
NSNumber *index = [self.itemIndexs objectForKey:indexPath];
if (index.integerValue < faces.faces.count) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index.integerValue inSection:groupIndex];
[self faceView:self didSelectItemAtIndexPath:indexPath];
}
}
- (void)faceView:(TUIFaceView *)faceView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
TUIFaceGroup *group = faceView.faceGroups[indexPath.section];
TUIFaceCellData *face = group.faces[indexPath.row];
if (indexPath.section == 0) {
NSString *faceName = face.name;
NSLog(@"FaceName:%@", faceName);
[self updateRecentMenuQueue:faceName];
[self updateReactClick:faceName];
if (self.delegateView) {
[self.delegateView hideWithAnimation];
}
}
}
- (void)updateRecentMenuQueue:(NSString *)faceName {
id<TUIEmojiMeditorProtocol> service = [[TIMCommonMediator share] getObject:@protocol(TUIEmojiMeditorProtocol)];
return [service updateRecentMenuQueue:faceName];
}
- (void)updateReactClick:(NSString *)faceName {
if (self.delegateView.targetCellData) {
[self.delegateView.targetCellData updateReactClick:faceName];
}
}
@end

View File

@@ -0,0 +1,25 @@
//
// TUIReactPopRecentView.h
// TUIChat
//
// Created by wyl on 2022/5/25.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <TUIChat/TUIChatPopMenuDefine.h>
#import <TUIChat/TUIChatPopMenu.h>
@class TUIReactPopRecentView;
NS_ASSUME_NONNULL_BEGIN
@interface TUIReactPopRecentView : UIView
@property(nonatomic, strong, readonly) NSMutableArray *faceGroups;
@property(nonatomic, assign) BOOL needShowbottomLine;
@property(nonatomic, strong) UIButton *arrowButton;
@property(nonatomic, weak) TUIChatPopMenu *delegateView;
- (void)setData:(NSMutableArray *)data;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,205 @@
//
// TUIReactPopRecentView.m
// TUIChat
//
// Created by wyl on 2022/5/25.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIReactPopRecentView.h"
#import <TIMCommon/NSString+TUIEmoji.h>
#import <TIMCommon/TIMCommonModel.h>
#import <TIMCommon/TIMDefine.h>
#import <TIMCommon/TUIFitButton.h>
#import <TIMCommon/TIMCommonMediator.h>
#import <TIMCommon/TUIEmojiMeditorProtocol.h>
#import "TUIEmojiReactDataProvider.h"
#import "TUIMessageCellData+Reaction.h"
#define kTIMRecentDefaultEmojiSize CGSizeMake(30, 30)
@interface TUIReactPopRecentView ()
@property(nonatomic, strong) NSMutableArray *sectionIndexInGroup;
@property(nonatomic, strong) NSMutableArray *pageCountInGroup;
@property(nonatomic, strong) NSMutableArray *groupIndexInSection;
@property(nonatomic, strong) NSMutableDictionary *itemIndexs;
@property(nonatomic, assign) NSInteger sectionCount;
@end
@implementation TUIReactPopRecentView
- (void)layoutSubviews {
[super layoutSubviews];
[self defaultLayout];
if (isRTL()) {
for (UIView *subview in self.subviews) {
if ([subview respondsToSelector:@selector(resetFrameToFitRTL)]) {
[subview resetFrameToFitRTL];
}
}
}
}
- (void)defaultLayout {
[self setupCorner];
[self setupDefaultArray];
}
- (void)setupCorner {
UIRectCorner corner = UIRectCornerTopRight | UIRectCornerTopLeft;
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corner cornerRadii:CGSizeMake(5, 5)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
}
- (void)setupDefaultArray {
NSMutableArray *faceArray = [NSMutableArray array];
TUIFaceGroup *defaultFaceGroup = [self findFaceGroupAboutType];
if (defaultFaceGroup) {
[faceArray addObject:defaultFaceGroup];
}
[self setData:faceArray];
}
- (void)setData:(NSMutableArray *)data {
_faceGroups = data;
_sectionIndexInGroup = [NSMutableArray array];
_groupIndexInSection = [NSMutableArray array];
_itemIndexs = [NSMutableDictionary dictionary];
NSInteger sectionIndex = 0;
for (NSInteger groupIndex = 0; groupIndex < _faceGroups.count; ++groupIndex) {
TUIFaceGroup *group = _faceGroups[groupIndex];
[_sectionIndexInGroup addObject:@(sectionIndex)];
int itemCount = group.rowCount * group.itemCountPerRow;
int sectionCount = ceil(group.faces.count * 1.0 / (itemCount - 0));
for (int sectionIndex = 0; sectionIndex < sectionCount; ++sectionIndex) {
[_groupIndexInSection addObject:@(groupIndex)];
}
sectionIndex += sectionCount;
}
_sectionCount = sectionIndex;
for (NSInteger curSection = 0; curSection < _sectionCount; ++curSection) {
NSNumber *groupIndex = _groupIndexInSection[curSection];
NSNumber *groupSectionIndex = _sectionIndexInGroup[groupIndex.integerValue];
TUIFaceGroup *face = _faceGroups[groupIndex.integerValue];
NSInteger itemCount = face.rowCount * face.itemCountPerRow;
NSInteger groupSection = curSection - groupSectionIndex.integerValue;
for (NSInteger itemIndex = 0; itemIndex < itemCount; ++itemIndex) {
// transpose line/row
NSInteger row = itemIndex % face.rowCount;
NSInteger column = itemIndex / face.rowCount;
NSInteger reIndex = face.itemCountPerRow * row + column + groupSection * itemCount;
[_itemIndexs setObject:@(reIndex) forKey:[NSIndexPath indexPathForRow:itemIndex inSection:curSection]];
}
}
[self createBtns];
if (self.needShowbottomLine) {
float margin = 20;
UIView *line = [[UIView alloc] initWithFrame:CGRectMake(margin, self.frame.size.height - 1, self.frame.size.width - 2 * margin, 0.5)];
;
[self addSubview:line];
line.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB");
}
}
- (TUIFaceGroup *)findFaceGroupAboutType {
// emoji group
id<TUIEmojiMeditorProtocol> service = [[TIMCommonMediator share] getObject:@protocol(TUIEmojiMeditorProtocol)];
TUIFaceGroup * group = [service getChatPopMenuRecentQueue];
group.faces = [NSMutableArray arrayWithArray:[group.faces subarrayWithRange:NSMakeRange(0, 6)]];
return group;
}
- (void)createBtns {
if (self.subviews) {
for (UIView *subView in self.subviews) {
[subView removeFromSuperview];
}
}
int groupIndex = [_groupIndexInSection[0] intValue];
TUIFaceGroup *group = _faceGroups[groupIndex];
int tag = 0;
float margin = 20;
float padding = 10;
UIButton *preBtn = nil;
for (TUIFaceCellData *cellData in group.faces) {
UIButton *button = [self buttonWithCellImage:[[TUIImageCache sharedInstance] getFaceFromCache:cellData.path] Tag:tag];
[self addSubview:button];
if (tag == 0) {
button.mm_width(kTIMRecentDefaultEmojiSize.width).mm_height(kTIMRecentDefaultEmojiSize.height).mm_left(margin).mm__centerY(self.mm_centerY);
} else {
button.mm_width(kTIMRecentDefaultEmojiSize.width).mm_height(kTIMRecentDefaultEmojiSize.height).mm_left(preBtn.mm_x + preBtn.mm_w + padding).mm__centerY(self.mm_centerY);
}
tag++;
preBtn = button;
}
self.arrowButton = [self buttonWithCellImage:TUIChatBundleThemeImage(@"chat_icon_emojiArrowDown_img", @"emojiArrowDown") Tag:999];
[self addSubview:self.arrowButton];
[self.arrowButton setImage:TUIChatBundleThemeImage(@"chat_icon_emojiArrowUp_img", @"emojiArrowUp") forState:UIControlStateSelected];
self.arrowButton.mm_width(25).mm_height(25).mm_right(margin).mm__centerY(self.mm_centerY);
}
- (UIButton *)buttonWithCellImage:(UIImage *)img Tag:(NSInteger)tag {
TUIFitButton *actionButton = [TUIFitButton buttonWithType:UIButtonTypeCustom];
actionButton.imageSize = CGSizeMake(kTIMRecentDefaultEmojiSize.width, kTIMRecentDefaultEmojiSize.height);
[actionButton setImage:img forState:UIControlStateNormal];
actionButton.contentMode = UIViewContentModeScaleAspectFit;
[actionButton addTarget:self action:@selector(onClick:) forControlEvents:UIControlEventTouchUpInside];
actionButton.tag = tag;
return actionButton;
}
- (void)onClick:(UIButton *)btn {
if (btn.tag == 999) {
btn.selected = !btn.selected;
if (btn.selected){
[self showDetailPage];
}
else {
[self hideDetailPage];
}
} else {
TUIFaceGroup *group = self.faceGroups[0];
TUIFaceCellData *face = group.faces[btn.tag];
NSString *faceName = face.name;
[self updateReactClick:faceName];
if (_delegateView) {
[_delegateView hideWithAnimation];
}
}
}
- (void)showDetailPage {
_delegateView.containerView.alpha = 0;
for (UIView *subview in _delegateView.emojiContainerView.subviews) {
subview.alpha = 1;
}
}
- (void)hideDetailPage {
_delegateView.containerView.alpha = 1;
for (UIView *subview in _delegateView.emojiContainerView.subviews) {
if (subview != self){
subview.alpha = 0;
}
}
}
- (void)updateReactClick:(NSString *)faceName {
if (self.delegateView.targetCellData) {
[self.delegateView.targetCellData updateReactClick:faceName];
}
}
@end

View File

@@ -0,0 +1,31 @@
//
// TUIReactPreview.h
// TUIChat
//
// Created by wyl on 2022/5/26.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <UIKit/UIKit.h>
@class TUIMessageCell;
@class TUIReactModel;
NS_ASSUME_NONNULL_BEGIN
@interface TUIReactPreview : UIView
/// datasource
@property(nonatomic, strong) NSMutableArray *listArrM;
/// Select the model for the TAB
@property(nonatomic, copy) void (^emojiClickCallback)(TUIReactModel *model);
@property(nonatomic, copy) void (^userClickCallback)(TUIReactModel *model);
@property(nonatomic, weak) TUIMessageCell *delegateCell;
- (void)updateView;
- (void)refreshByArray:(NSMutableArray *)tagsArray;
- (void)notifyReactionChanged;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,190 @@
//
// TUIReactPreview.m
// TUIChat
//
// Created by wyl on 2022/5/26.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIReactPreview.h"
#import "TUIReactPreviewCell.h"
#import "TUIReactModel.h"
#import <TIMCommon/NSString+TUIEmoji.h>
#import <TIMCommon/TUIMessageCell.h>
#import <TUICore/TUICore.h>
@interface TUIReactPreview ()
@end
@implementation TUIReactPreview
#pragma mark - View init
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self){
}
return self;
}
- (NSMutableArray *)listArrM {
if (!_listArrM) {
_listArrM = [NSMutableArray arrayWithCapacity:3];
}
return _listArrM;
}
- (void)updateView {
for (UIView *subView in self.subviews) {
if (subView) {
[subView removeFromSuperview];
}
}
if (self.listArrM.count <= 0) {
return;
}
/**
* Margin, including topbottomleft and right
*/
CGFloat margin = 12;
CGFloat rightmargin = 12;
CGFloat topMargin = 3;
CGFloat bottomMargin = 3;
/**
* Padding in the horizontal direction
*/
CGFloat padding = 6;
/**
* Padding in the vertical direction
*/
CGFloat verticalPadding = 8;
/**
* Size of tagview
*/
CGFloat tagViewWidth = 0;
CGFloat tagViewHeight = 0;
int index = 0;
TUIReactPreviewCell *preCell = nil;
for (TUIReactModel *model in self.listArrM) {
TUIReactPreviewCell *cell = [[TUIReactPreviewCell alloc] initWithFrame:CGRectZero];
cell.tag = index;
cell.model = model;
[self addSubview:cell];
if (index == 0) {
cell.frame = CGRectMake(margin, topMargin, cell.ItemWidth, 24);
tagViewWidth = cell.ItemWidth;
tagViewHeight = 24;
if (self.listArrM.count == 1) {
/**
* If there is only one tag
*/
tagViewWidth = margin + cell.frame.size.width + rightmargin;
tagViewHeight = cell.frame.origin.y + cell.frame.size.height + bottomMargin;
}
} else {
CGFloat previousFrameRightPoint = preCell.frame.origin.x + preCell.frame.size.width;
/**
* Placed in the current line, the width required after layout
*/
CGFloat needWidth = (padding + cell.ItemWidth);
CGFloat residueWidth = (MaxTagSize - previousFrameRightPoint - rightmargin);
BOOL condation = (needWidth < residueWidth);
if (condation) {
/**
* Placed it in the same line if has enough space
*/
cell.frame = CGRectMake(previousFrameRightPoint + padding, preCell.frame.origin.y, cell.ItemWidth, 24);
} else {
/**
* Placed it in the another line if not enough space
*/
cell.frame = CGRectMake(margin, preCell.frame.origin.y + preCell.frame.size.height + verticalPadding, cell.ItemWidth, 24);
}
CGFloat curretLineMaxWidth = MAX(previousFrameRightPoint + rightmargin, cell.frame.origin.x + cell.frame.size.width + rightmargin);
tagViewWidth = MAX(curretLineMaxWidth, tagViewWidth);
tagViewHeight = cell.frame.origin.y + cell.frame.size.height + bottomMargin;
}
__weak typeof(self) weakSelf = self;
cell.emojiClickCallback = ^(TUIReactModel *_Nonnull model) {
NSLog(@"model.emojiKey click : %@", model.emojiKey);
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf.emojiClickCallback) {
strongSelf.emojiClickCallback(model);
}
};
cell.userClickCallback = ^(TUIReactModel *_Nonnull model) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf.userClickCallback) {
strongSelf.userClickCallback(model);
}
};
preCell = cell;
index++;
}
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, tagViewWidth, tagViewHeight);
}
- (void)updateRTLView {
for (UIView *subView in self.subviews) {
if (subView) {
[subView resetFrameToFitRTL];
}
}
}
- (void)notifyReactionChanged {
NSDictionary *param = @{TUICore_TUIPluginNotify_DidChangePluginViewSubKey_Data : self.delegateCell.messageData,
TUICore_TUIPluginNotify_DidChangePluginViewSubKey_VC : self,
TUICore_TUIPluginNotify_DidChangePluginViewSubKey_isAllowScroll2Bottom: @"0"};
[TUICore notifyEvent:TUICore_TUIPluginNotify
subKey:TUICore_TUIPluginNotify_DidChangePluginViewSubKey
object:nil
param:param];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// tell constraints they need updating
[self setNeedsUpdateConstraints];
// update constraints now so we can animate the change
[self updateConstraintsIfNeeded];
[self layoutIfNeeded];
});
}
- (void)refreshByArray:(NSMutableArray *)tagsArray {
if (tagsArray && tagsArray.count > 0) {
self.listArrM = [NSMutableArray arrayWithArray:tagsArray];
[UIView animateWithDuration:1 animations:^{
[self updateView];
}completion:^(BOOL finished) {
}];
self.delegateCell.messageData.messageContainerAppendSize = self.frame.size;
[self notifyReactionChanged];
}
else {
self.listArrM = [NSMutableArray array];
[UIView animateWithDuration:1 animations:^{
[self updateView];
}completion:^(BOOL finished) {
}];
if (!CGSizeEqualToSize(self.delegateCell.messageData.messageContainerAppendSize, CGSizeZero)) {
self.delegateCell.messageData.messageContainerAppendSize = CGSizeZero;
[self notifyReactionChanged];
}
}
}
@end

View File

@@ -0,0 +1,24 @@
//
// TUIReactPreviewCell.h
// TUIChat
//
// Created by wyl on 2022/5/26.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <UIKit/UIKit.h>
@class TUIReactModel;
#define MaxTagSize [UIScreen mainScreen].bounds.size.width * 0.25 * 3 - 20
NS_ASSUME_NONNULL_BEGIN
@interface TUIReactPreviewCell : UIView
@property(nonatomic, strong) TUIReactModel *model;
@property(nonatomic, assign) CGFloat ItemWidth;
@property(nonatomic, copy) void (^emojiClickCallback)(TUIReactModel *model);
@property(nonatomic, copy) void (^userClickCallback)(TUIReactModel *model);
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,132 @@
//
// TUIReactPreviewCell.m
// TUIChat
//
// Created by wyl on 2022/5/26.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIReactPreviewCell.h"
#import <TIMCommon/TIMCommonModel.h>
#import "TUIAttributedLabel.h"
#import "TUIReactModel.h"
#define margin 8
#define rightMargin 8
#define maxItemWidth 107
@interface TUIReactPreviewCell () <TUIAttributedLabelDelegate>
@property(nonatomic, strong) UIButton *tagBtn;
@end
@implementation TUIReactPreviewCell
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self prepareUI];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
[self prepareUI];
}
return self;
}
#pragma mark - UI
- (void)prepareUI {
self.layer.cornerRadius = 12.0f;
self.layer.masksToBounds = YES;
}
#pragma mark - Data
- (void)setModel:(TUIReactModel *)model {
_model = model;
self.backgroundColor = model.defaultColor;
for (UIView *subView in self.subviews) {
[subView removeFromSuperview];
}
NSString *text = [model descriptionFollowUserStr];
CGFloat allWidth = 0;
UIButton *emojiBtn = [[UIButton alloc] init];
[emojiBtn addTarget:self action:@selector(emojiBtnClick) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:emojiBtn];
[emojiBtn setImage:[[TUIImageCache sharedInstance] getFaceFromCache:model.emojiPath] forState:UIControlStateNormal];
emojiBtn.frame = CGRectMake(margin, 4, 18, 18);
allWidth += (emojiBtn.frame.size.width);
UIView *line = [[UIView alloc] initWithFrame:CGRectMake(emojiBtn.frame.origin.x + emojiBtn.frame.size.width + 4, (30 - 14) * 0.5, 1, 14)];
line.backgroundColor = [UIColor colorWithRed:68 / 255.0 green:68 / 255.0 blue:68 / 255.0 alpha:0.2];
[self addSubview:line];
allWidth += line.frame.size.width;
TUIAttributedLabel *label = [[TUIAttributedLabel alloc] initWithFrame:CGRectMake(line.frame.origin.x + 4, 8, self.frame.size.width, 30)];
label.userInteractionEnabled = YES;
[self addSubview:label];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onSelectUser)];
[label addGestureRecognizer:tap];
NSString *contentString = text;
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineSpacing:11];
[paragraphStyle setLineBreakMode:NSLineBreakByTruncatingTail];
NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:contentString];
[mutableAttributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:[contentString rangeOfString:contentString]];
[mutableAttributedString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:11] range:[contentString rangeOfString:contentString]];
[mutableAttributedString addAttribute:NSStrokeWidthAttributeName value:@0 range:[contentString rangeOfString:contentString]];
[label setText:mutableAttributedString];
label.linkAttributes = @{(NSString *)kCTForegroundColorAttributeName:model.textColor,
(NSString *)kCTUnderlineStyleAttributeName:[NSNumber numberWithBool:NO]};
[label addLinkToURL:[NSURL URLWithString:@"click"] withRange:[contentString rangeOfString:contentString]];
[label sizeToFit];
label.textColor = model.textColor;
CGFloat fitWidth = label.frame.size.width;
if (fitWidth > (MaxTagSize - allWidth - 12 - 12 - 100)) {
fitWidth = MaxTagSize - allWidth - 12 - 12 - 100;
}
// label.textInsets = UIEdgeInsetsMake(0, 0, 0, 0);
label.frame = CGRectMake(label.frame.origin.x, label.frame.origin.y, fitWidth, label.frame.size.height);
label.preferredMaxLayoutWidth = 10;
// self.contentView.frame.size.width - 28 -16 -1
label.font = [UIFont systemFontOfSize:11];
label.numberOfLines = 1;
label.delegate = (id)self;
allWidth += label.frame.size.width;
allWidth += margin;
allWidth += rightMargin;
allWidth += 8;
self.ItemWidth = allWidth;
}
- (void)layoutSubviews {
[super layoutSubviews];
for (UIView *subView in self.subviews) {
[subView resetFrameToFitRTL];
}
}
- (void)emojiBtnClick {
if (self.emojiClickCallback) {
self.emojiClickCallback(self.model);
}
}
- (void)onSelectUser {
if (self.userClickCallback) {
self.userClickCallback(self.model);
}
}
@end

View File

@@ -0,0 +1,22 @@
//
// TUIReactPreview_Minimalist.h
// TUIEmojiPlugin
//
// Created by cologne on 2023/11/29.
//
#import <UIKit/UIKit.h>
#import <TIMCommon/TUIMessageCell.h>
@class TUIReactModel;
NS_ASSUME_NONNULL_BEGIN
@interface TUIReactPreview_Minimalist : UIView
@property(nonatomic, strong) NSMutableArray<TUIReactModel *> *reactlistArr;
@property(nonatomic, weak) TUIMessageCell *delegateCell;
@property(nonatomic, copy) void (^emojiClickCallback)(TUIReactModel *model);
- (void)refreshByArray:(NSMutableArray *)tagsArray;
- (void)updateView;
- (void)notifyReactionChanged;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,212 @@
//
// TUIReactPreview_Minimalist.m
// TUIEmojiPlugin
//
// Created by cologne on 2023/11/29.
//
#import "TUIReactPreview_Minimalist.h"
#import <TUICore/TUIDefine.h>
#import <TIMCommon/TIMDefine.h>
#import <TIMCommon/NSString+TUIEmoji.h>
#import <TUICore/TUICore.h>
#import "TUIReactModel.h"
@interface TUIReactPreview_Minimalist ()
@property(nonatomic, strong) UIImageView *replyEmojiView;
@property(nonatomic, strong) UILabel *replyEmojiCount;
@property(nonatomic, strong) NSMutableArray *replyEmojiImageViews;
@end
@implementation TUIReactPreview_Minimalist
- (instancetype)init {
self = [super init];
if (self) {
[self setupViews];
}
return self;
}
- (void)setupViews {
_replyEmojiView = [[UIImageView alloc] initWithFrame:CGRectZero];
_replyEmojiView.layer.borderWidth = 1;
_replyEmojiView.layer.borderColor = RGBA(221, 221, 221, 1).CGColor;
_replyEmojiView.layer.masksToBounds = true;
_replyEmojiView.layer.cornerRadius = 10;
_replyEmojiView.backgroundColor = [UIColor whiteColor];
_replyEmojiView.userInteractionEnabled = YES;
[self addSubview:_replyEmojiView];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onJumpToReactEmojiPage)];
[self addGestureRecognizer:tap];
_replyEmojiCount = [[UILabel alloc] init];
[_replyEmojiCount setTextColor:RGBA(153, 153, 153, 1)];
_replyEmojiCount.rtlAlignment = TUITextRTLAlignmentLeading;
[_replyEmojiCount setFont:[UIFont systemFontOfSize:12]];
[_replyEmojiView addSubview:_replyEmojiCount];
_replyEmojiImageViews = [NSMutableArray array];
}
+ (BOOL)requiresConstraintBasedLayout {
return YES;
}
- (void)updateView {
// emoji
if (_replyEmojiImageViews.count > 0) {
for (UIImageView *emojiView in _replyEmojiImageViews) {
[emojiView removeFromSuperview];
}
[_replyEmojiImageViews removeAllObjects];
}
_replyEmojiView.hidden = YES;
_replyEmojiCount.hidden = YES;
if (self.reactlistArr.count > 0) {
_replyEmojiView.hidden = NO;
_replyEmojiCount.hidden = NO;
NSInteger emojiCount = 0;
NSInteger emojiMaxCount = 6;
NSInteger replyEmojiTotalCount = 0;
NSMutableDictionary *existEmojiMap = [NSMutableDictionary dictionary];
for (TUIReactModel *model in self.reactlistArr) {
if (!model.emojiKey) {
continue;
}
replyEmojiTotalCount += model.followIDs.count;
if (emojiCount >= emojiMaxCount || existEmojiMap[model.emojiKey]) {
continue;
}
UIImageView *emojiView = [[UIImageView alloc] init];
if (emojiCount < emojiMaxCount - 1) {
existEmojiMap[model.emojiKey] = model;
[emojiView setImage:[model.emojiKey getEmojiImage]];
} else {
[emojiView setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_reply_more_icon")]];
}
[_replyEmojiView addSubview:emojiView];
[_replyEmojiImageViews addObject:emojiView];
emojiCount++;
}
_replyEmojiCount.text = [@(replyEmojiTotalCount) stringValue];
}
// tell constraints they need updating
[self setNeedsUpdateConstraints];
// update constraints now so we can animate the change
[self updateConstraintsIfNeeded];
[self layoutIfNeeded];
}
// this is Apple's recommended place for adding/updating constraints
- (void)updateConstraints {
[super updateConstraints];
if (_replyEmojiImageViews.count > 0) {
CGFloat emojiSize = 12;
CGFloat emojiSpace = kScale390(4);
__block UIImageView * preEmojiView = nil;
for (int i = 0; i < _replyEmojiImageViews.count; ++i) {
UIImageView *emojiView = _replyEmojiImageViews[i];
if (i == 0) {
preEmojiView = nil;
}
else {
preEmojiView = _replyEmojiImageViews[i-1];
}
[emojiView mas_remakeConstraints:^(MASConstraintMaker *make) {
if (i == 0){
make.leading.mas_equalTo(_replyEmojiView.mas_leading).mas_offset(kScale390(8));
}
else {
make.leading.mas_equalTo(preEmojiView.mas_trailing).mas_offset(emojiSpace);
}
make.width.height.mas_equalTo(emojiSize);
make.centerY.mas_equalTo(_replyEmojiView.mas_centerY);
}];
emojiView.layer.masksToBounds = YES;
emojiView.layer.cornerRadius = emojiSize / 2.0;
}
UIImageView * lastEmojiView = _replyEmojiImageViews.lastObject;
[_replyEmojiCount mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(lastEmojiView.mas_trailing).mas_offset(kScale390(8));
make.trailing.mas_equalTo(_replyEmojiView.mas_trailing);
make.width.mas_equalTo(emojiSize + 10);
make.height.mas_equalTo(emojiSize);
make.centerY.mas_equalTo(_replyEmojiView.mas_centerY);
}];
[_replyEmojiView mas_remakeConstraints:^(MASConstraintMaker *make) {
if (self.delegateCell.messageData.direction == MsgDirectionIncoming) {
make.leading.mas_greaterThanOrEqualTo(self);
} else {
make.trailing.mas_lessThanOrEqualTo(self);
}
make.top.mas_equalTo(self);
make.height.mas_equalTo(self);
}];
} else {
_replyEmojiCount.frame = CGRectZero;
_replyEmojiView.frame = CGRectZero;
}
}
- (NSMutableArray *)reactlistArr {
if (!_reactlistArr) {
_reactlistArr = [NSMutableArray array];
}
return _reactlistArr;
}
- (void)refreshByArray:(NSMutableArray *)tagsArray {
if (tagsArray && tagsArray.count > 0) {
self.reactlistArr = [NSMutableArray arrayWithArray:tagsArray];
[UIView animateWithDuration:1 animations:^{
[self updateView];
}completion:^(BOOL finished) {
}];
self.delegateCell.messageData.messageContainerAppendSize = CGSizeMake(0, kScale375(16));
[self notifyReactionChanged];
}
else {
self.reactlistArr = [NSMutableArray array];
[UIView animateWithDuration:1 animations:^{
[self updateView];
}completion:^(BOOL finished) {
}];
if (!CGSizeEqualToSize(self.delegateCell.messageData.messageContainerAppendSize, CGSizeZero)) {
self.delegateCell.messageData.messageContainerAppendSize = CGSizeZero;
[self notifyReactionChanged];
}
}
}
- (void)notifyReactionChanged {
NSDictionary *param = @{TUICore_TUIPluginNotify_DidChangePluginViewSubKey_Data : self.delegateCell.messageData,
TUICore_TUIPluginNotify_DidChangePluginViewSubKey_VC : self,
TUICore_TUIPluginNotify_DidChangePluginViewSubKey_isAllowScroll2Bottom:@"0"};
[TUICore notifyEvent:TUICore_TUIPluginNotify
subKey:TUICore_TUIPluginNotify_DidChangePluginViewSubKey
object:nil
param:param];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// tell constraints they need updating
[self setNeedsUpdateConstraints];
// update constraints now so we can animate the change
[self updateConstraintsIfNeeded];
[self layoutIfNeeded];
});
}
- (void)onJumpToReactEmojiPage {
if (self.emojiClickCallback) {
self.emojiClickCallback(nil);
}
}
@end

View File

@@ -0,0 +1,18 @@
//
// TUIReactUtil.h
// Masonry
//
// Created by cologne on 2023/12/26.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@import ImSDK_Plus;
static const long long TUIReactCommercialAbility = 1LL << 48;
@interface TUIReactUtil : NSObject
+ (void)checkCommercialAbility;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,67 @@
//
// TUIReactUtil.m
// Masonry
//
// Created by cologne on 2023/12/26.
//
#import "TUIReactUtil.h"
#import <TUICore/TUITool.h>
#import <TUICore/TUICommonModel.h>
#import <TUICore/TUIGlobalization.h>
static BOOL gEnableReact = NO;
@implementation TUIReactUtil
+ (TUIReactUtil *)sharedInstance {
static dispatch_once_t onceToken;
static TUIReactUtil * g_sharedInstance = nil;
dispatch_once(&onceToken, ^{
g_sharedInstance = [[TUIReactUtil alloc] init];
});
return g_sharedInstance;
}
- (instancetype)init {
if (self = [super init]) {
}
return self;
}
#pragma mark - report
+ (void)reportTUIReactComponentUsage {
NSMutableDictionary *param = [NSMutableDictionary dictionary];
param[@"UIComponentType"] = @(18);
param[@"UIStyleType"] = @(0);
NSData *dataParam = [NSJSONSerialization dataWithJSONObject:param options:NSJSONWritingPrettyPrinted error:nil];
NSString *strParam = [[NSString alloc] initWithData:dataParam encoding:NSUTF8StringEncoding];
[[V2TIMManager sharedInstance] callExperimentalAPI:@"reportTUIComponentUsage"
param:strParam
succ:^(NSObject *result) {
NSLog(@"success");
}
fail:^(int code, NSString *desc){
}];
}
+ (void)checkCommercialAbility {
[TUITool checkCommercialAbility:TUIReactCommercialAbility succ:^(BOOL enabled) {
gEnableReact = enabled;
if (gEnableReact) {
[self.class reportTUIReactComponentUsage];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setBool:YES forKey:@"TUIReactionCommercialAbility"];
[userDefaults synchronize];
}
} fail:^(int code, NSString *desc) {
gEnableReact = NO;
}];
}
+ (BOOL)isReactServiceSupported {
return gEnableReact;
}
@end