增加换肤功能
This commit is contained in:
60
TUIKit/TUIEmojiPlugin/Resources/PrivacyInfo.xcprivacy
Normal file
60
TUIKit/TUIEmojiPlugin/Resources/PrivacyInfo.xcprivacy
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypePhotosorVideos</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeProductPersonalization</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeAudioData</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeProductPersonalization</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeUserID</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeProductPersonalization</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
26
TUIKit/TUIEmojiPlugin/TUIEmojiPlugin.podspec
Normal file
26
TUIKit/TUIEmojiPlugin/TUIEmojiPlugin.podspec
Normal file
@@ -0,0 +1,26 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'TUIEmojiPlugin'
|
||||
spec.version = '8.5.6864'
|
||||
spec.platform = :ios
|
||||
spec.ios.deployment_target = '9.0'
|
||||
spec.license = { :type => 'Proprietary',
|
||||
:text => <<-LICENSE
|
||||
copyright 2017 tencent Ltd. All rights reserved.
|
||||
LICENSE
|
||||
}
|
||||
spec.homepage = 'https://cloud.tencent.com/document/product/269/3794'
|
||||
spec.documentation_url = 'https://cloud.tencent.com/document/product/269/9147'
|
||||
spec.authors = 'tencent video cloud'
|
||||
spec.summary = 'TUIEmojiPlugin'
|
||||
spec.dependency 'TUICore'
|
||||
spec.dependency 'TIMCommon'
|
||||
spec.dependency 'TUIChat'
|
||||
spec.requires_arc = true
|
||||
|
||||
spec.source = { :git => './'}
|
||||
spec.source_files = '**/*.{h,m,mm,c}'
|
||||
spec.resource = ['Resources/*.bundle']
|
||||
spec.resource_bundle = {
|
||||
"#{spec.module_name}_Privacy" => 'Resources/PrivacyInfo.xcprivacy'
|
||||
}
|
||||
end
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
60
TUIKit/TUIEmojiPlugin/UI/DataProvider/TUIReactModel.h
Normal file
60
TUIKit/TUIEmojiPlugin/UI/DataProvider/TUIReactModel.h
Normal 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
|
||||
92
TUIKit/TUIEmojiPlugin/UI/DataProvider/TUIReactModel.m
Normal file
92
TUIKit/TUIEmojiPlugin/UI/DataProvider/TUIReactModel.m
Normal 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
|
||||
16
TUIKit/TUIEmojiPlugin/UI/Service/TUIEmojiExtensionObserver.h
Normal file
16
TUIKit/TUIEmojiPlugin/UI/Service/TUIEmojiExtensionObserver.h
Normal 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
|
||||
328
TUIKit/TUIEmojiPlugin/UI/Service/TUIEmojiExtensionObserver.m
Normal file
328
TUIKit/TUIEmojiPlugin/UI/Service/TUIEmojiExtensionObserver.m
Normal 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
|
||||
@@ -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
|
||||
126
TUIKit/TUIEmojiPlugin/UI/UI/MemberDetaiPage/TUIReactMemberCell.m
Normal file
126
TUIKit/TUIEmojiPlugin/UI/UI/MemberDetaiPage/TUIReactMemberCell.m
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
203
TUIKit/TUIEmojiPlugin/UI/UI/PopUI/TUIReactPopContextRecentView.m
Normal file
203
TUIKit/TUIEmojiPlugin/UI/UI/PopUI/TUIReactPopContextRecentView.m
Normal 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
|
||||
20
TUIKit/TUIEmojiPlugin/UI/UI/PopUI/TUIReactPopEmojiView.h
Normal file
20
TUIKit/TUIEmojiPlugin/UI/UI/PopUI/TUIReactPopEmojiView.h
Normal 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
|
||||
127
TUIKit/TUIEmojiPlugin/UI/UI/PopUI/TUIReactPopEmojiView.m
Normal file
127
TUIKit/TUIEmojiPlugin/UI/UI/PopUI/TUIReactPopEmojiView.m
Normal 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
|
||||
25
TUIKit/TUIEmojiPlugin/UI/UI/PopUI/TUIReactPopRecentView.h
Normal file
25
TUIKit/TUIEmojiPlugin/UI/UI/PopUI/TUIReactPopRecentView.h
Normal 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
|
||||
205
TUIKit/TUIEmojiPlugin/UI/UI/PopUI/TUIReactPopRecentView.m
Normal file
205
TUIKit/TUIEmojiPlugin/UI/UI/PopUI/TUIReactPopRecentView.m
Normal 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
|
||||
31
TUIKit/TUIEmojiPlugin/UI/UI/ReactPreview/TUIReactPreview.h
Normal file
31
TUIKit/TUIEmojiPlugin/UI/UI/ReactPreview/TUIReactPreview.h
Normal 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
|
||||
190
TUIKit/TUIEmojiPlugin/UI/UI/ReactPreview/TUIReactPreview.m
Normal file
190
TUIKit/TUIEmojiPlugin/UI/UI/ReactPreview/TUIReactPreview.m
Normal 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 top、bottom、left 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
|
||||
@@ -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
|
||||
132
TUIKit/TUIEmojiPlugin/UI/UI/ReactPreview/TUIReactPreviewCell.m
Normal file
132
TUIKit/TUIEmojiPlugin/UI/UI/ReactPreview/TUIReactPreviewCell.m
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
18
TUIKit/TUIEmojiPlugin/UI/Util/TUIReactUtil.h
Normal file
18
TUIKit/TUIEmojiPlugin/UI/Util/TUIReactUtil.h
Normal 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
|
||||
67
TUIKit/TUIEmojiPlugin/UI/Util/TUIReactUtil.m
Normal file
67
TUIKit/TUIEmojiPlugin/UI/Util/TUIReactUtil.m
Normal 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
|
||||
Reference in New Issue
Block a user