提交
This commit is contained in:
23
TUIKit/TUIChat/CommonModel/TUIAIDenoiseSignatureManager.h
Normal file
23
TUIKit/TUIChat/CommonModel/TUIAIDenoiseSignatureManager.h
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
// TUIAIDenoiseSignatureManager.h
|
||||
// TUIChat
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIAIDenoiseSignatureManager : NSObject
|
||||
|
||||
@property(nonatomic, copy, readonly) NSString *signature;
|
||||
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
- (void)updateSignature;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
61
TUIKit/TUIChat/CommonModel/TUIAIDenoiseSignatureManager.m
Normal file
61
TUIKit/TUIChat/CommonModel/TUIAIDenoiseSignatureManager.m
Normal file
@@ -0,0 +1,61 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
// TUIAIDenoiseSignatureManager.m
|
||||
// TUIChat
|
||||
//
|
||||
|
||||
#import "TUIAIDenoiseSignatureManager.h"
|
||||
#import <ImSDK_Plus/ImSDK_Plus.h>
|
||||
|
||||
static TUIAIDenoiseSignatureManager *gSharedInstance = nil;
|
||||
static NSString *const kAPIKey = @"getAIDenoiseSignature";
|
||||
static NSString *const kSignatureKey = @"signature";
|
||||
static NSString *const kExpiredTimeKey = @"expired_time";
|
||||
|
||||
@interface TUIAIDenoiseSignatureManager ()
|
||||
@property(nonatomic, copy, readwrite) NSString *signature;
|
||||
@property(nonatomic, assign) NSTimeInterval expiredTime;
|
||||
@end
|
||||
|
||||
@implementation TUIAIDenoiseSignatureManager
|
||||
|
||||
+ (instancetype)sharedInstance {
|
||||
static dispatch_once_t once_token;
|
||||
dispatch_once(&once_token, ^{
|
||||
gSharedInstance = [[TUIAIDenoiseSignatureManager alloc] init];
|
||||
});
|
||||
return gSharedInstance;
|
||||
}
|
||||
|
||||
- (void)updateSignature {
|
||||
NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];
|
||||
if (currentTime < self.expiredTime) {
|
||||
return;
|
||||
}
|
||||
[[V2TIMManager sharedInstance] callExperimentalAPI:kAPIKey
|
||||
param:nil
|
||||
succ:^(NSObject *result) {
|
||||
if (result == nil || ![result isKindOfClass:NSDictionary.class]) {
|
||||
return;
|
||||
}
|
||||
NSDictionary *dict = (NSDictionary *)result;
|
||||
if (dict[kSignatureKey] != nil && [dict[kSignatureKey] isKindOfClass:NSString.class]) {
|
||||
self.signature = dict[kSignatureKey];
|
||||
}
|
||||
if (dict[kExpiredTimeKey] != nil && [dict[kExpiredTimeKey] isKindOfClass:NSNumber.class]) {
|
||||
self.expiredTime = [dict[kExpiredTimeKey] doubleValue];
|
||||
}
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
NSLog(@"getAIDenoiseSignature failed, code: %d, desc: %@", code, desc);
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)signature {
|
||||
[self updateSignature];
|
||||
return _signature;
|
||||
}
|
||||
|
||||
@end
|
||||
37
TUIKit/TUIChat/CommonModel/TUIAudioRecorder.h
Normal file
37
TUIKit/TUIChat/CommonModel/TUIAudioRecorder.h
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
// TUIAudioRecorder.h
|
||||
// TUIChat
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/// TUIAudioRecorder is designed for recording audio when sending audio message.
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIAudioRecorder;
|
||||
@protocol TUIAudioRecorderDelegate <NSObject>
|
||||
|
||||
- (void)audioRecorder:(TUIAudioRecorder *)recorder didCheckPermission:(BOOL)isGranted isFirstTime:(BOOL)isFirstTime;
|
||||
/// Power value can be used to simulate the animation of mic changes when speaking.
|
||||
- (void)audioRecorder:(TUIAudioRecorder *)recorder didPowerChanged:(float)power;
|
||||
- (void)audioRecorder:(TUIAudioRecorder *)recorder didRecordTimeChanged:(NSTimeInterval)time;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIAudioRecorder : NSObject
|
||||
|
||||
@property(nonatomic, weak) id<TUIAudioRecorderDelegate> delegate;
|
||||
|
||||
@property(nonatomic, copy, readonly) NSString *recordedFilePath;
|
||||
|
||||
- (void)record;
|
||||
- (void)stop;
|
||||
- (void)cancel;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
361
TUIKit/TUIChat/CommonModel/TUIAudioRecorder.m
Normal file
361
TUIKit/TUIChat/CommonModel/TUIAudioRecorder.m
Normal file
@@ -0,0 +1,361 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
// TUIAudioRecorder.m
|
||||
// TUIChat
|
||||
//
|
||||
|
||||
#import "TUIAudioRecorder.h"
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUILogin.h>
|
||||
#import "TUIAIDenoiseSignatureManager.h"
|
||||
|
||||
@interface TUIAudioRecorder () <AVAudioRecorderDelegate, TUINotificationProtocol>
|
||||
|
||||
@property(nonatomic, strong) AVAudioRecorder *recorder;
|
||||
@property(nonatomic, strong) NSTimer *recordTimer;
|
||||
|
||||
@property(nonatomic, assign) BOOL isUsingCallKitRecorder;
|
||||
|
||||
@property(nonatomic, copy, readwrite) NSString *recordedFilePath;
|
||||
@property(nonatomic, assign) NSTimeInterval currentRecordTime;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIAudioRecorder
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self configNotify];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)configNotify {
|
||||
[TUICore registerEvent:TUICore_RecordAudioMessageNotify subKey:TUICore_RecordAudioMessageNotify_RecordAudioVoiceVolumeSubKey object:self];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[TUICore unRegisterEventByObject:self];
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
- (void)record {
|
||||
[self checkMicPermissionWithCompletion:^(BOOL isGranted, BOOL isFirstChek) {
|
||||
if (TUILogin.getCurrentBusinessScene != None) {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitMessageTypeOtherUseMic) duration:3];
|
||||
return;
|
||||
}
|
||||
if (isFirstChek) {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(audioRecorder:didCheckPermission:isFirstTime:)]) {
|
||||
[self.delegate audioRecorder:self didCheckPermission:isGranted isFirstTime:YES];
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(audioRecorder:didCheckPermission:isFirstTime:)]) {
|
||||
[self.delegate audioRecorder:self didCheckPermission:isGranted isFirstTime:NO];
|
||||
}
|
||||
if (isGranted) {
|
||||
[self createRecordedFilePath];
|
||||
if (![self startCallKitRecording]) {
|
||||
[self startSystemRecording];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
[self stopRecordTimer];
|
||||
|
||||
if (self.isUsingCallKitRecorder) {
|
||||
[self stopCallKitRecording];
|
||||
} else {
|
||||
[self stopSystemRecording];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancel {
|
||||
[self stopRecordTimer];
|
||||
|
||||
if (self.isUsingCallKitRecorder) {
|
||||
[self stopCallKitRecording];
|
||||
} else {
|
||||
[self cancelSystemRecording];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
- (void)createRecordedFilePath {
|
||||
self.recordedFilePath = [TUIKit_Voice_Path stringByAppendingString:[TUITool genVoiceName:nil withExtension:@"m4a"]];
|
||||
}
|
||||
|
||||
- (void)stopRecordTimer {
|
||||
if (self.recordTimer) {
|
||||
[self.recordTimer invalidate];
|
||||
self.recordTimer = nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark-- Timer
|
||||
- (void)triggerRecordTimer {
|
||||
self.currentRecordTime = 0;
|
||||
self.recordTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(onRecordTimerTriggered:) userInfo:nil repeats:YES];
|
||||
}
|
||||
|
||||
- (void)onRecordTimerTriggered:(NSTimer *)timer {
|
||||
[self.recorder updateMeters];
|
||||
|
||||
if (self.isUsingCallKitRecorder) {
|
||||
/// To ensure the callkit recorder's recording time is enough for 60 seconds.
|
||||
self.currentRecordTime += 0.2;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(audioRecorder:didRecordTimeChanged:)]) {
|
||||
[self.delegate audioRecorder:self didRecordTimeChanged:self.currentRecordTime];
|
||||
}
|
||||
} else {
|
||||
float power = [self.recorder averagePowerForChannel:0];
|
||||
NSTimeInterval currentTime = self.recorder.currentTime;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(audioRecorder:didPowerChanged:)]) {
|
||||
[self.delegate audioRecorder:self didPowerChanged:power];
|
||||
}
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(audioRecorder:didRecordTimeChanged:)]) {
|
||||
[self.delegate audioRecorder:self didRecordTimeChanged:currentTime];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)checkMicPermissionWithCompletion:(void (^)(BOOL isGranted, BOOL isFirstChek))completion {
|
||||
AVAudioSessionRecordPermission permission = AVAudioSession.sharedInstance.recordPermission;
|
||||
|
||||
/**
|
||||
* For the first request for authorization after a new installation, it is necessary to
|
||||
* determine whether it is Undetermined again to avoid errors.
|
||||
*/
|
||||
if (permission == AVAudioSessionRecordPermissionDenied || permission == AVAudioSessionRecordPermissionUndetermined) {
|
||||
[AVAudioSession.sharedInstance requestRecordPermission:^(BOOL granted) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (completion) {
|
||||
completion(granted, YES);
|
||||
}
|
||||
});
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL isGranted = permission == AVAudioSessionRecordPermissionGranted;
|
||||
if (completion) {
|
||||
completion(isGranted, NO);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark-- Record audio using system framework
|
||||
- (void)startSystemRecording {
|
||||
self.isUsingCallKitRecorder = NO;
|
||||
|
||||
AVAudioSession *session = [AVAudioSession sharedInstance];
|
||||
NSError *error = nil;
|
||||
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
|
||||
[session setActive:YES error:&error];
|
||||
|
||||
NSDictionary *recordSetting = [[NSDictionary alloc] initWithObjectsAndKeys:
|
||||
/**
|
||||
* Sampling rate: 8000/11025/22050/44100/96000 (this parameter affects the audio
|
||||
* quality)
|
||||
*/
|
||||
[NSNumber numberWithFloat:16000.0], AVSampleRateKey,
|
||||
/**
|
||||
* Audio format
|
||||
*/
|
||||
[NSNumber numberWithInt:kAudioFormatMPEG4AAC], AVFormatIDKey,
|
||||
/**
|
||||
* Sampling bits: 8, 16, 24, 32, default is 16
|
||||
*/
|
||||
[NSNumber numberWithInt:16], AVLinearPCMBitDepthKey,
|
||||
/**
|
||||
* Number of audio channels 1 or 2
|
||||
*/
|
||||
[NSNumber numberWithInt:1], AVNumberOfChannelsKey,
|
||||
/**
|
||||
* Recording quality
|
||||
*/
|
||||
[NSNumber numberWithInt:AVAudioQualityHigh], AVEncoderAudioQualityKey, nil];
|
||||
|
||||
[self createRecordedFilePath];
|
||||
|
||||
NSURL *url = [NSURL fileURLWithPath:self.recordedFilePath];
|
||||
self.recorder = [[AVAudioRecorder alloc] initWithURL:url settings:recordSetting error:nil];
|
||||
self.recorder.meteringEnabled = YES;
|
||||
[self.recorder prepareToRecord];
|
||||
[self.recorder record];
|
||||
[self.recorder updateMeters];
|
||||
|
||||
[self triggerRecordTimer];
|
||||
NSLog(@"start system recording");
|
||||
}
|
||||
|
||||
- (void)stopSystemRecording {
|
||||
if (AVAudioSession.sharedInstance.recordPermission == AVAudioSessionRecordPermissionDenied) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([self.recorder isRecording]) {
|
||||
[self.recorder stop];
|
||||
}
|
||||
|
||||
self.recorder = nil;
|
||||
NSLog(@"stop system recording");
|
||||
}
|
||||
|
||||
- (void)cancelSystemRecording {
|
||||
if ([self.recorder isRecording]) {
|
||||
[self.recorder stop];
|
||||
}
|
||||
|
||||
NSString *path = self.recorder.url.path;
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
||||
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
|
||||
}
|
||||
|
||||
self.recorder = nil;
|
||||
NSLog(@"cancel system recording");
|
||||
}
|
||||
|
||||
#pragma mark-- Record audio using TUICallKit framework
|
||||
- (BOOL)startCallKitRecording {
|
||||
if (![TUICore getService:TUICore_TUIAudioMessageRecordService]) {
|
||||
NSLog(@"TUICallKit audio recording service does not exist");
|
||||
return NO;
|
||||
}
|
||||
NSString *signature = [TUIAIDenoiseSignatureManager sharedInstance].signature;
|
||||
if (signature.length == 0) {
|
||||
NSLog(@"denoise signature is empty");
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSMutableDictionary *audioRecordParam = [[NSMutableDictionary alloc] init];
|
||||
[audioRecordParam setValue:signature forKey:TUICore_TUIAudioMessageRecordService_StartRecordAudioMessageMethod_SignatureKey];
|
||||
[audioRecordParam setValue:@([TUILogin getSdkAppID]) forKey:TUICore_TUIAudioMessageRecordService_StartRecordAudioMessageMethod_SdkappidKey];
|
||||
[audioRecordParam setValue:self.recordedFilePath forKey:TUICore_TUIAudioMessageRecordService_StartRecordAudioMessageMethod_PathKey];
|
||||
|
||||
@weakify(self);
|
||||
void (^startCallBack)(NSInteger errorCode, NSString *errorMessage, NSDictionary *param) =
|
||||
^(NSInteger errorCode, NSString *errorMessage, NSDictionary *param) {
|
||||
@strongify(self);
|
||||
NSString *method = param[@"method"];
|
||||
if ([method isEqualToString:TUICore_RecordAudioMessageNotify_StartRecordAudioMessageSubKey]) {
|
||||
[self onTUICallKitRecordStarted:errorCode];
|
||||
}
|
||||
};
|
||||
|
||||
[TUICore callService:TUICore_TUIAudioMessageRecordService
|
||||
method:TUICore_TUIAudioMessageRecordService_StartRecordAudioMessageMethod
|
||||
param:audioRecordParam
|
||||
resultCallback:startCallBack];
|
||||
|
||||
self.isUsingCallKitRecorder = YES;
|
||||
NSLog(@"start TUICallKit recording");
|
||||
return true;
|
||||
}
|
||||
|
||||
- (void)stopCallKitRecording {
|
||||
@weakify(self);
|
||||
void (^stopCallBack)(NSInteger errorCode, NSString *errorMessage, NSDictionary *param) =
|
||||
^(NSInteger errorCode, NSString *errorMessage, NSDictionary *param) {
|
||||
@strongify(self);
|
||||
NSString *method = param[@"method"];
|
||||
if ([method isEqualToString:TUICore_RecordAudioMessageNotify_StopRecordAudioMessageSubKey]) {
|
||||
[self onTUICallKitRecordCompleted:errorCode];
|
||||
}
|
||||
};
|
||||
|
||||
[TUICore callService:TUICore_TUIAudioMessageRecordService
|
||||
method:TUICore_TUIAudioMessageRecordService_StopRecordAudioMessageMethod
|
||||
param:nil
|
||||
resultCallback:stopCallBack];
|
||||
|
||||
NSLog(@"stop TUICallKit recording");
|
||||
}
|
||||
|
||||
#pragma mark - TUINotificationProtocol
|
||||
- (void)onNotifyEvent:(NSString *)key subKey:(NSString *)subKey object:(nullable id)anObject param:(NSDictionary *)param {
|
||||
if ([key isEqualToString:TUICore_RecordAudioMessageNotify]) {
|
||||
if (param == nil) {
|
||||
NSLog(@"TUICallKit notify param is invalid");
|
||||
return;
|
||||
}
|
||||
if ([subKey isEqualToString:TUICore_RecordAudioMessageNotify_RecordAudioVoiceVolumeSubKey]) {
|
||||
NSUInteger volume = [param[@"volume"] unsignedIntegerValue];
|
||||
[self onTUICallKitVolumeChanged:volume];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onTUICallKitRecordStarted:(NSInteger)errorCode {
|
||||
switch (errorCode) {
|
||||
case TUICore_RecordAudioMessageNotifyError_None: {
|
||||
[self triggerRecordTimer];
|
||||
break;
|
||||
}
|
||||
case TUICore_RecordAudioMessageNotifyError_MicPermissionRefused: {
|
||||
break;
|
||||
}
|
||||
case TUICore_RecordAudioMessageNotifyError_StatusInCall: {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitInputRecordRejectedInCall)];
|
||||
break;
|
||||
}
|
||||
case TUICore_RecordAudioMessageNotifyError_StatusIsAudioRecording: {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitInputRecordRejectedIsRecording)];
|
||||
break;
|
||||
}
|
||||
case TUICore_RecordAudioMessageNotifyError_RequestAudioFocusFailed:
|
||||
case TUICore_RecordAudioMessageNotifyError_RecordInitFailed:
|
||||
case TUICore_RecordAudioMessageNotifyError_PathFormatNotSupport:
|
||||
case TUICore_RecordAudioMessageNotifyError_MicStartFail:
|
||||
case TUICore_RecordAudioMessageNotifyError_MicNotAuthorized:
|
||||
case TUICore_RecordAudioMessageNotifyError_MicSetParamFail:
|
||||
case TUICore_RecordAudioMessageNotifyError_MicOccupy: {
|
||||
[self stopCallKitRecording];
|
||||
NSLog(@"start TUICallKit recording failed, errorCode: %ld", (long)errorCode);
|
||||
break;
|
||||
}
|
||||
case TUICore_RecordAudioMessageNotifyError_InvalidParam:
|
||||
case TUICore_RecordAudioMessageNotifyError_SignatureError:
|
||||
case TUICore_RecordAudioMessageNotifyError_SignatureExpired:
|
||||
default: {
|
||||
[self stopCallKitRecording];
|
||||
[self startSystemRecording];
|
||||
NSLog(@"start TUICallKit recording failed, errorCode: %ld, switch to system recorder", (long)errorCode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onTUICallKitRecordCompleted:(NSInteger)errorCode {
|
||||
switch (errorCode) {
|
||||
case TUICore_RecordAudioMessageNotifyError_None: {
|
||||
[self stopRecordTimer];
|
||||
break;
|
||||
}
|
||||
case TUICore_RecordAudioMessageNotifyError_NoMessageToRecord:
|
||||
case TUICore_RecordAudioMessageNotifyError_RecordFailed: {
|
||||
NSLog(@"stop TUICallKit recording failed, errorCode: %ld", (long)errorCode);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onTUICallKitVolumeChanged:(NSUInteger)volume {
|
||||
/// Adapt volume to power.
|
||||
float power = (NSInteger)volume - 90;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(audioRecorder:didPowerChanged:)]) {
|
||||
[self.delegate audioRecorder:self didPowerChanged:power];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
273
TUIKit/TUIChat/CommonModel/TUIChatConfig.h
Normal file
273
TUIKit/TUIChat/CommonModel/TUIChatConfig.h
Normal file
@@ -0,0 +1,273 @@
|
||||
//
|
||||
// TUIChatConfig.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/6/10.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TIMCommon/TUIMessageCellData.h>
|
||||
#import "TUIChatConversationModel.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIChatEventConfig;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, TUIChatRegisterCustomMessageStyleType) {
|
||||
TUIChatRegisterCustomMessageStyleTypeClassic = 0,
|
||||
TUIChatRegisterCustomMessageStyleTypeMinimalist = 1,
|
||||
};
|
||||
|
||||
@class TUICustomActionSheetItem;
|
||||
@class TUIChatConversationModel;
|
||||
typedef NS_OPTIONS(NSInteger, TUIChatInputBarMoreMenuItem) {
|
||||
TUIChatInputBarMoreMenuItem_None = 0,
|
||||
TUIChatInputBarMoreMenuItem_CustomMessage = 1 << 0,
|
||||
TUIChatInputBarMoreMenuItem_TakePhoto = 1 << 1,
|
||||
TUIChatInputBarMoreMenuItem_RecordVideo = 1 << 2,
|
||||
TUIChatInputBarMoreMenuItem_Album = 1 << 3,
|
||||
TUIChatInputBarMoreMenuItem_File = 1 << 4,
|
||||
TUIChatInputBarMoreMenuItem_Room = 1 << 5,
|
||||
TUIChatInputBarMoreMenuItem_Poll = 1 << 6,
|
||||
TUIChatInputBarMoreMenuItem_GroupNote = 1 << 7,
|
||||
TUIChatInputBarMoreMenuItem_VideoCall = 1 << 8,
|
||||
TUIChatInputBarMoreMenuItem_AudioCall = 1 << 9,
|
||||
};
|
||||
@protocol TUIChatInputBarConfigDataSource <NSObject>
|
||||
@optional
|
||||
/**
|
||||
* Implement this method to hide items in more menu of the specified model.
|
||||
*/
|
||||
- (TUIChatInputBarMoreMenuItem)inputBarShouldHideItemsInMoreMenuOfModel:(TUIChatConversationModel *)model;
|
||||
/**
|
||||
* Implement this method to add new items to the more menu of the specified model only for the classic version.
|
||||
*/
|
||||
- (NSArray<TUIInputMoreCellData *> *)inputBarShouldAddNewItemsToMoreMenuOfModel:(TUIChatConversationModel *)model;
|
||||
/**
|
||||
* Implement this method to add new items to the more list of the specified model only for the minimalist version.
|
||||
*/
|
||||
- (NSArray<TUICustomActionSheetItem *> *)inputBarShouldAddNewItemsToMoreListOfModel:(TUIChatConversationModel *)model;
|
||||
@end
|
||||
|
||||
@protocol TUIChatShortcutViewDataSource <NSObject>
|
||||
@optional
|
||||
- (NSArray<TUIChatShortcutMenuCellData *> *)itemsInShortcutViewOfModel:(TUIChatConversationModel *)model;
|
||||
- (UIColor *)shortcutViewBackgroundColorOfModel:(TUIChatConversationModel *)model;
|
||||
- (CGFloat)shortcutViewHeightOfModel:(TUIChatConversationModel *)model;
|
||||
@end
|
||||
|
||||
|
||||
@interface TUIChatConfig : NSObject
|
||||
|
||||
+ (TUIChatConfig *)defaultConfig;
|
||||
|
||||
@property(nonatomic, strong) NSArray<TUIFaceGroup *> *chatContextEmojiDetailGroups;
|
||||
|
||||
/**
|
||||
* A read receipt is required to send a message, default is No
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL msgNeedReadReceipt;
|
||||
|
||||
/**
|
||||
* Display the video call button, if the TUICalling component is integrated, the default is YES
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL enableVideoCall;
|
||||
|
||||
/**
|
||||
* Whether to display the audio call button, if the TUICalling component is integrated, the default is YES
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL enableAudioCall;
|
||||
|
||||
/**
|
||||
* Display custom welcome message button, default YES
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL enableWelcomeCustomMessage;
|
||||
|
||||
/**
|
||||
* In the chat interface, long press the pop-up box to display the emoji interactive message function, the default is YES
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL enablePopMenuEmojiReactAction;
|
||||
|
||||
/**
|
||||
* Chat long press the pop-up box to display the message reply function entry, the default is YES
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL enablePopMenuReplyAction;
|
||||
|
||||
/**
|
||||
* Chat long press the pop-up box to display the entry of the message reference function, the default is YES
|
||||
*/
|
||||
|
||||
@property(nonatomic, assign) BOOL enablePopMenuReferenceAction;
|
||||
|
||||
@property(nonatomic, assign) BOOL enablePopMenuPinAction;
|
||||
@property(nonatomic, assign) BOOL enablePopMenuRecallAction;
|
||||
@property(nonatomic, assign) BOOL enablePopMenuTranslateAction;
|
||||
@property(nonatomic, assign) BOOL enablePopMenuConvertAction;
|
||||
@property(nonatomic, assign) BOOL enablePopMenuForwardAction;
|
||||
@property(nonatomic, assign) BOOL enablePopMenuSelectAction;
|
||||
@property(nonatomic, assign) BOOL enablePopMenuCopyAction;
|
||||
@property(nonatomic, assign) BOOL enablePopMenuDeleteAction;
|
||||
@property(nonatomic, assign) BOOL enablePopMenuInfoAction;
|
||||
@property(nonatomic, assign) BOOL enablePopMenuAudioPlaybackAction;
|
||||
|
||||
/**
|
||||
* Whether the C2C chat dialog box displays "The other party is typing...", the default is YES
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL enableTypingStatus;
|
||||
|
||||
/**
|
||||
* Whether the chat dialog box displays "InputBar", the default is YES
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL enableMainPageInputBar;
|
||||
|
||||
/**
|
||||
* Setup the backgroud color of chat page
|
||||
*/
|
||||
@property(nonatomic, strong) UIColor *backgroudColor;
|
||||
|
||||
/**
|
||||
* Setup the backgroud image of chat page
|
||||
*/
|
||||
@property(nonatomic, strong) UIImage *backgroudImage;
|
||||
|
||||
/**
|
||||
* Whether to turn on audio and video call suspension windows, default is YES
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL enableFloatWindowForCall;
|
||||
|
||||
/**
|
||||
* Whether to enable multi-terminal login function for audio and video calls, default is NO
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL enableMultiDeviceForCall;
|
||||
|
||||
/**
|
||||
* Set whether to enable incoming banner when user received audio and video calls, default is false
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL enableIncomingBanner;
|
||||
|
||||
/**
|
||||
* Set whether to enable the virtual background for audio and video calls, default value is false
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL enableVirtualBackgroundForCall;
|
||||
|
||||
/**
|
||||
* The time interval for message recall, in seconds, default is 120 seconds. If you want to adjust this configuration, please modify the IM console settings
|
||||
* synchronously.
|
||||
* https://cloud.tencent.com/document/product/269/38656#.E6.B6.88.E6.81.AF.E6.92.A4.E5.9B.9E.E8.AE.BE.E7.BD.AE
|
||||
*/
|
||||
@property(nonatomic, assign) NSUInteger timeIntervalForMessageRecall;
|
||||
|
||||
/**
|
||||
不超过 60s
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat maxAudioRecordDuration;
|
||||
|
||||
/**
|
||||
不超过 15s
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat maxVideoRecordDuration;
|
||||
|
||||
@property(nonatomic, assign) BOOL showRoomButton;
|
||||
@property(nonatomic, assign) BOOL showPollButton;
|
||||
@property(nonatomic, assign) BOOL showGroupNoteButton;
|
||||
@property(nonatomic, assign) BOOL showRecordVideoButton;
|
||||
@property(nonatomic, assign) BOOL showTakePhotoButton;
|
||||
@property(nonatomic, assign) BOOL showAlbumButton;
|
||||
@property(nonatomic, assign) BOOL showFileButton;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This class is used to register event listeners for Chat from external sources, to listen for various events in Chat and respond accordingly,
|
||||
* such as listening for avatar click events, long-press message events, etc.
|
||||
* You need to set a delegate for the implementation method: TUIChatConfig.defaultConfig.eventConfig.chatEventListener = "YourDelegateViewController".
|
||||
* YourDelegateViewController needs to conform to the <TUIChatEventListener> protocol and implement the protocol method.
|
||||
* Taking - (BOOL)onUserIconClicked:messageCellData: as an example, returning NO indicates an insertion behavior,
|
||||
* which is not intercepted and will be further processed by the Chat module.
|
||||
* Taking - (BOOL)onUserIconClicked:messageCellData: as an example, returning YES indicates an override behavior,
|
||||
* which will be intercepted and only the overridden method will be executed. The Chat module will not continue to process it.
|
||||
*/
|
||||
@property(nonatomic, strong) TUIChatEventConfig * eventConfig;
|
||||
/**
|
||||
* DataSource for inputBar.
|
||||
*/
|
||||
@property (nonatomic, weak) id<TUIChatInputBarConfigDataSource> inputBarDataSource;
|
||||
/**
|
||||
* DataSource for shortcutView above inputBar.
|
||||
*/
|
||||
@property (nonatomic, weak) id<TUIChatShortcutViewDataSource> shortcutViewDataSource;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIChatEventListener <NSObject>
|
||||
|
||||
/**
|
||||
* This callback is triggered when a user avatar in the chat list interface is clicked. Returning YES indicates that this event has been intercepted,
|
||||
* and Chat will not process it further. Returning NO indicates that this event is not intercepted, and Chat will continue to process it.
|
||||
*/
|
||||
- (BOOL)onUserIconClicked:(UIView *)view messageCellData:(TUIMessageCellData *)celldata;
|
||||
/**
|
||||
* This callback is triggered when a user avatar in the chat list interface is long-pressed. Returning YES indicates that this event has been intercepted,
|
||||
* and Chat will not process it further. Returning NO indicates that this event is not intercepted, and Chat will continue to process it.
|
||||
*/
|
||||
- (BOOL)onUserIconLongClicked:(UIView *)view messageCellData:(TUIMessageCellData *)celldata;
|
||||
|
||||
/**
|
||||
* This callback is triggered when a message in the chat list interface is clicked. Returning YES indicates that this event has been intercepted,
|
||||
* and Chat will not process it further. Returning NO indicates that this event is not intercepted, and Chat will continue to process it.
|
||||
*/
|
||||
- (BOOL)onMessageClicked:(UIView *)view messageCellData:(TUIMessageCellData *)celldata;
|
||||
|
||||
/**
|
||||
* This callback is triggered when a message in the chat list interface is long-pressed. Returning YES indicates that this event has been intercepted,
|
||||
* and Chat will not process it further. Returning NO indicates that this event is not intercepted, and Chat will continue to process it.
|
||||
*/
|
||||
- (BOOL)onMessageLongClicked:(UIView *)view messageCellData:(TUIMessageCellData *)celldata;
|
||||
@end
|
||||
|
||||
@interface TUIChatEventConfig : NSObject
|
||||
@property (nonatomic,weak)id <TUIChatEventListener>chatEventListener;
|
||||
@end
|
||||
|
||||
// Regiser custom message category
|
||||
// You can call this method like :
|
||||
//
|
||||
// [TUIChatConfig.defaultConfig registerCustomMessage:@"YourBusinessID"
|
||||
// messageCellClassName:@"YourCustomCellNameString"
|
||||
// messageCellDataClassName:@"YourCustomCellDataNameString"];
|
||||
@interface TUIChatConfig (CustomMessageRegiser)
|
||||
|
||||
/**
|
||||
* Register custom message , by default, register to the classic UI.
|
||||
* param businessID Custom message businessID (note that it must be unique)
|
||||
* param cellName Custom message messagCell type
|
||||
* param cellDataName Custom message MessagCellData type
|
||||
*/
|
||||
- (void)registerCustomMessage:(NSString *)businessID
|
||||
messageCellClassName:(NSString *)cellName
|
||||
messageCellDataClassName:(NSString *)cellDataName;
|
||||
|
||||
/**
|
||||
* Register custom message
|
||||
* param businessID Custom message businessID (note that it must be unique)
|
||||
* param cellName Custom message messagCell type
|
||||
* param cellDataName Custom message MessagCellData type
|
||||
* param styleType UI style corresponding to this custom message, for example TUIChatRegisterCustomMessageStyleTypeClassic
|
||||
*/
|
||||
- (void)registerCustomMessage:(NSString *)businessID
|
||||
messageCellClassName:(NSString *)cellName
|
||||
messageCellDataClassName:(NSString *)cellDataName
|
||||
styleType:(TUIChatRegisterCustomMessageStyleType)styleType;
|
||||
@end
|
||||
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
122
TUIKit/TUIChat/CommonModel/TUIChatConfig.m
Normal file
122
TUIKit/TUIChat/CommonModel/TUIChatConfig.m
Normal file
@@ -0,0 +1,122 @@
|
||||
//
|
||||
// TUIChatConfig.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/6/10.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIChatConfig.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TIMCommon/TIMCommonMediator.h>
|
||||
#import <TIMCommon/TUIEmojiMeditorProtocol.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
@implementation TUIChatConfig
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.msgNeedReadReceipt = YES;
|
||||
self.enableVideoCall = YES;
|
||||
self.enableAudioCall = YES;
|
||||
self.enableWelcomeCustomMessage = YES;
|
||||
self.showFileButton = YES;
|
||||
self.showAlbumButton = YES;
|
||||
self.showTakePhotoButton = YES;
|
||||
self.showRecordVideoButton = YES;
|
||||
self.showGroupNoteButton = YES;
|
||||
self.showPollButton = YES;
|
||||
self.showRoomButton = YES;
|
||||
self.enablePopMenuEmojiReactAction = YES;
|
||||
self.enablePopMenuReplyAction = YES;
|
||||
self.enablePopMenuReferenceAction = YES;
|
||||
self.enablePopMenuPinAction = YES;
|
||||
self.enablePopMenuRecallAction = YES;
|
||||
self.enablePopMenuTranslateAction = YES;
|
||||
self.enablePopMenuConvertAction = YES;
|
||||
self.enablePopMenuForwardAction = YES;
|
||||
self.enablePopMenuSelectAction = YES;
|
||||
self.enablePopMenuCopyAction = YES;
|
||||
self.enablePopMenuDeleteAction = YES;
|
||||
self.enablePopMenuInfoAction = YES;
|
||||
self.enablePopMenuAudioPlaybackAction = YES;
|
||||
self.enableMainPageInputBar = YES;
|
||||
self.enableTypingStatus = YES;
|
||||
self.enableFloatWindowForCall = YES;
|
||||
self.enableMultiDeviceForCall = NO;
|
||||
self.enableIncomingBanner = YES;
|
||||
self.enableVirtualBackgroundForCall = NO;
|
||||
self.timeIntervalForMessageRecall = 120;
|
||||
self.maxAudioRecordDuration = 60;
|
||||
self.maxVideoRecordDuration = 15;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (TUIChatConfig *)defaultConfig {
|
||||
static dispatch_once_t onceToken;
|
||||
static TUIChatConfig *config;
|
||||
dispatch_once(&onceToken, ^{
|
||||
config = [[TUIChatConfig alloc] init];
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
- (NSArray<TUIFaceGroup *> *)chatContextEmojiDetailGroups {
|
||||
id<TUIEmojiMeditorProtocol> service = [[TIMCommonMediator share] getObject:@protocol(TUIEmojiMeditorProtocol)];
|
||||
return [service getChatContextEmojiDetailGroups];
|
||||
}
|
||||
|
||||
- (TUIChatEventConfig *)eventConfig {
|
||||
if (!_eventConfig) {
|
||||
_eventConfig = [[TUIChatEventConfig alloc] init];
|
||||
}
|
||||
return _eventConfig;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIChatEventConfig
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation TUIChatConfig (CustomMessageRegiser)
|
||||
|
||||
- (void)registerCustomMessage:(NSString *)businessID
|
||||
messageCellClassName:(NSString *)cellName
|
||||
messageCellDataClassName:(NSString *)cellDataName {
|
||||
[self registerCustomMessage:businessID
|
||||
messageCellClassName:cellName
|
||||
messageCellDataClassName:cellDataName
|
||||
styleType:TUIChatRegisterCustomMessageStyleTypeClassic];
|
||||
}
|
||||
|
||||
- (void)registerCustomMessage:(NSString *)businessID
|
||||
messageCellClassName:(NSString *)cellName
|
||||
messageCellDataClassName:(NSString *)cellDataName
|
||||
styleType:(TUIChatRegisterCustomMessageStyleType)styleType {
|
||||
|
||||
if (businessID.length <0 || cellName.length <0 ||cellDataName.length <0) {
|
||||
NSLog(@"registerCustomMessage Error, check info %s", __func__);
|
||||
return;
|
||||
}
|
||||
NSString * serviceName = @"";
|
||||
if (styleType == TUIChatRegisterCustomMessageStyleTypeClassic) {
|
||||
serviceName = TUICore_TUIChatService;
|
||||
}
|
||||
else {
|
||||
serviceName = TUICore_TUIChatService_Minimalist;
|
||||
}
|
||||
[TUICore callService:serviceName
|
||||
method:TUICore_TUIChatService_AppendCustomMessageMethod
|
||||
param:@{BussinessID : businessID,
|
||||
TMessageCell_Name : cellName,
|
||||
TMessageCell_Data_Name : cellDataName
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
121
TUIKit/TUIChat/CommonModel/TUIChatConversationModel.h
Normal file
121
TUIKit/TUIChat/CommonModel/TUIChatConversationModel.h
Normal file
@@ -0,0 +1,121 @@
|
||||
//
|
||||
// TUIChatConversationModel.h
|
||||
// TXIMSDK_TUIKit_iOS
|
||||
//
|
||||
// Created by kayev on 2021/8/12.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@import UIKit;
|
||||
@class TUIChatShortcutMenuCellData;
|
||||
@class TUIInputMoreCellData;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIChatConversationModel : NSObject
|
||||
|
||||
/**
|
||||
* UniqueID for a conversation
|
||||
*/
|
||||
@property(nonatomic, strong) NSString *conversationID;
|
||||
|
||||
/**
|
||||
* If the conversation type is group chat, the groupID means group id
|
||||
*/
|
||||
@property(nonatomic, strong) NSString *groupID;
|
||||
|
||||
/**
|
||||
* Group type
|
||||
*/
|
||||
@property(nonatomic, strong) NSString *groupType;
|
||||
|
||||
/**
|
||||
* If the conversation type is one-to-one chat, the userID means peer user id
|
||||
*/
|
||||
@property(nonatomic, strong) NSString *userID;
|
||||
|
||||
/**
|
||||
* title
|
||||
*/
|
||||
@property(nonatomic, strong) NSString *title;
|
||||
|
||||
/**
|
||||
* The avatar of the user or group corresponding to the conversation
|
||||
*/
|
||||
@property(nonatomic, strong) NSString *faceUrl;
|
||||
|
||||
/**
|
||||
* Image for avatar
|
||||
*/
|
||||
@property(nonatomic, strong) UIImage *avatarImage;
|
||||
|
||||
/**
|
||||
*
|
||||
* Conversation draft
|
||||
*/
|
||||
@property(nonatomic, strong) NSString *draftText;
|
||||
|
||||
/**
|
||||
* Group@ message tip string
|
||||
*/
|
||||
@property(nonatomic, strong) NSString *atTipsStr;
|
||||
|
||||
/**
|
||||
* Sequence list of group-at message
|
||||
*/
|
||||
@property(nonatomic, strong) NSMutableArray<NSNumber *> *atMsgSeqs;
|
||||
|
||||
/**
|
||||
* The input status of the other Side (C2C Only)
|
||||
*/
|
||||
|
||||
@property(nonatomic, assign) BOOL otherSideTyping;
|
||||
|
||||
/**
|
||||
* A read receipt is required to send a message, the default is YES
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL msgNeedReadReceipt;
|
||||
|
||||
/**
|
||||
* Display the video call button, if the TUICalling component is integrated, the default is YES
|
||||
*/
|
||||
|
||||
@property(nonatomic, assign) BOOL enableVideoCall;
|
||||
|
||||
/**
|
||||
* Whether to display the audio call button, if the TUICalling component is integrated, the default is YES
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL enableAudioCall;
|
||||
|
||||
/**
|
||||
* Display custom welcome message button, default YES
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL enableWelcomeCustomMessage;
|
||||
|
||||
@property(nonatomic, assign) BOOL enableRoom;
|
||||
|
||||
@property(nonatomic, assign) BOOL isLimitedPortraitOrientation;
|
||||
|
||||
@property(nonatomic, assign) BOOL enablePoll;
|
||||
|
||||
@property(nonatomic, assign) BOOL enableGroupNote;
|
||||
|
||||
@property(nonatomic, assign) BOOL enableTakePhoto;
|
||||
|
||||
@property(nonatomic, assign) BOOL enableRecordVideo;
|
||||
|
||||
@property(nonatomic, assign) BOOL enableAlbum;
|
||||
|
||||
@property(nonatomic, assign) BOOL enableFile;
|
||||
|
||||
@property (nonatomic, copy) NSArray *customizedNewItemsInMoreMenu;
|
||||
|
||||
@property (nonatomic, strong) UIColor *shortcutViewBackgroundColor;
|
||||
@property (nonatomic, assign) CGFloat shortcutViewHeight;
|
||||
@property (nonatomic, strong) NSArray<TUIChatShortcutMenuCellData *> *shortcutMenuItems;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
31
TUIKit/TUIChat/CommonModel/TUIChatConversationModel.m
Normal file
31
TUIKit/TUIChat/CommonModel/TUIChatConversationModel.m
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// TUIChatConversationModel.m
|
||||
// TXIMSDK_TUIKit_iOS
|
||||
//
|
||||
// Created by kayev on 2021/8/12.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIChatConversationModel.h"
|
||||
|
||||
@implementation TUIChatConversationModel
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self){
|
||||
self.msgNeedReadReceipt = YES;
|
||||
self.enableVideoCall = YES;
|
||||
self.enableAudioCall = YES;
|
||||
self.enableRoom = YES;
|
||||
self.enableWelcomeCustomMessage = YES;
|
||||
self.isLimitedPortraitOrientation = NO;
|
||||
self.enablePoll = YES;
|
||||
self.enableGroupNote = YES;
|
||||
self.enableTakePhoto = YES;
|
||||
self.enableRecordVideo = YES;
|
||||
self.enableAlbum = YES;
|
||||
self.enableFile = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
81
TUIKit/TUIChat/CommonModel/TUIChatDefine.h
Normal file
81
TUIKit/TUIChat/CommonModel/TUIChatDefine.h
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// TUIChatDefine.h
|
||||
// Pods
|
||||
//
|
||||
// Created by xiangzhang on 2022/10/14.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef TUI_COMPONENTS_IOS_TUICHAT_COMMONMODEL_TUICHATDEFINE_H_
|
||||
#define TUI_COMPONENTS_IOS_TUICHAT_COMMONMODEL_TUICHATDEFINE_H_
|
||||
|
||||
static NSString* const kMemberCellReuseId = @"kMemberCellReuseId";
|
||||
|
||||
typedef void (^TUIImageMessageDownloadCallback)(void);
|
||||
|
||||
typedef void (^TUIVideoMessageDownloadCallback)(void);
|
||||
|
||||
typedef void (^TUIReplyAsyncLoadFinish)(void);
|
||||
|
||||
typedef void (^TUIInputPreviewBarCallback)(void);
|
||||
|
||||
typedef void (^TUIReplyQuoteAsyncLoadFinish)(void);
|
||||
|
||||
typedef void (^TUIChatSelectAllContentCallback)(BOOL);
|
||||
|
||||
typedef void (^TUIReferenceSelectAllContentCallback)(BOOL);
|
||||
|
||||
typedef void (^TUIReplySelectAllContentCallback)(BOOL);
|
||||
|
||||
typedef NS_ENUM(NSInteger, TUIMultiResultOption) {
|
||||
/**
|
||||
*
|
||||
* Get all selected results
|
||||
*/
|
||||
TUIMultiResultOptionAll = 0,
|
||||
/**
|
||||
*
|
||||
* Filter out data that does not support forwarding
|
||||
*/
|
||||
TUIMultiResultOptionFiterUnsupportRelay = 1 << 0,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, TUIMessageReadViewTag) {
|
||||
TUIMessageReadViewTagUnknown = 0, // unknown
|
||||
TUIMessageReadViewTagRead, // read group members
|
||||
TUIMessageReadViewTagUnread, // unread group members
|
||||
TUIMessageReadViewTagReadDisable, // disable read group members
|
||||
TUIMessageReadViewTagC2C, // c2c member
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, InputStatus) {
|
||||
Input_Status_Input,
|
||||
Input_Status_Input_Face,
|
||||
Input_Status_Input_More,
|
||||
Input_Status_Input_Keyboard,
|
||||
Input_Status_Input_Talk,
|
||||
Input_Status_Input_Camera,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, RecordStatus) {
|
||||
Record_Status_TooShort,
|
||||
Record_Status_TooLong,
|
||||
Record_Status_Recording,
|
||||
Record_Status_Cancel,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, TUIChatSmallTongueType) {
|
||||
TUIChatSmallTongueType_None,
|
||||
TUIChatSmallTongueType_ScrollToBoom,
|
||||
TUIChatSmallTongueType_ReceiveNewMsg,
|
||||
TUIChatSmallTongueType_SomeoneAt,
|
||||
};
|
||||
|
||||
#define TUITencentCloudHomePageCN @"https://cloud.tencent.com/document/product/269/68228"
|
||||
#define TUITencentCloudHomePageEN @"https://www.tencentcloud.com/document/product/1047/45913"
|
||||
|
||||
#define TUIChatSendMessageNotification @"TUIChatSendMessageNotification"
|
||||
#define TUIChatSendMessageWithoutUpdateUINotification @"TUIChatSendMessageWithoutUpdateUINotification"
|
||||
#define TUIChatInsertMessageWithoutUpdateUINotification @"TUIChatInsertMessageWithoutUpdateUINotification"
|
||||
|
||||
#endif // TUI_COMPONENTS_IOS_TUICHAT_COMMONMODEL_TUICHATDEFINE_H_
|
||||
32
TUIKit/TUIChat/CommonModel/TUIChatMediaSendingManager.h
Normal file
32
TUIKit/TUIChat/CommonModel/TUIChatMediaSendingManager.h
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// TUIChatMediaSendingManager.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by yiliangwang on 2025/1/6.
|
||||
// Copyright © 2025 Tencent. All rights reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <TIMCommon/TUIMessageCellData.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIChatMediaTask : NSObject
|
||||
@property (nonatomic, strong) TUIMessageCellData* placeHolderCellData;
|
||||
@property (nonatomic, copy) NSString *msgID;
|
||||
@property (nonatomic, copy) NSString *conversationID;
|
||||
@end
|
||||
|
||||
@interface TUIChatMediaSendingManager : NSObject
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSString *, TUIChatMediaTask *> *tasks;
|
||||
@property (nonatomic, strong) NSHashTable<UIViewController *> *mediaSendingControllers;
|
||||
+ (instancetype)sharedInstance;
|
||||
- (void)addMediaTask:(TUIChatMediaTask *)task forKey:(NSString *)key;
|
||||
- (void)updateProgress:(float)progress forKey:(NSString *)key;
|
||||
- (void)removeMediaTaskForKey:(NSString *)key;
|
||||
- (NSMutableArray<TUIChatMediaTask *> *)findPlaceHolderListByConversationID:(NSString *)conversationID;
|
||||
- (void)addCurrentVC:(UIViewController *)vc;
|
||||
- (void)removeCurrentVC:(UIViewController *)vc;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
62
TUIKit/TUIChat/CommonModel/TUIChatMediaSendingManager.m
Normal file
62
TUIKit/TUIChat/CommonModel/TUIChatMediaSendingManager.m
Normal file
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// TUIChatMediaSendingManager.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by yiliangwang on 2025/1/6.
|
||||
// Copyright © 2025 Tencent. All rights reserved.
|
||||
|
||||
|
||||
#import "TUIChatMediaSendingManager.h"
|
||||
|
||||
@implementation TUIChatMediaTask
|
||||
|
||||
@end
|
||||
@implementation TUIChatMediaSendingManager
|
||||
|
||||
+ (instancetype)sharedInstance {
|
||||
static TUIChatMediaSendingManager *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [[TUIChatMediaSendingManager alloc] init];
|
||||
instance.tasks = [NSMutableDictionary dictionary];
|
||||
instance.mediaSendingControllers = [NSHashTable weakObjectsHashTable];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (void)addMediaTask:(TUIChatMediaTask *)task forKey:(NSString *)key {
|
||||
self.tasks[key] = task;
|
||||
}
|
||||
|
||||
- (void)updateProgress:(float)progress forKey:(NSString *)key {
|
||||
TUIChatMediaTask *task = self.tasks[key];;
|
||||
|
||||
TUIMessageCellData *message = task.placeHolderCellData;
|
||||
if (message) {
|
||||
message.videoTranscodingProgress = progress;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeMediaTaskForKey:(NSString *)key {
|
||||
[self.tasks removeObjectForKey:key];
|
||||
}
|
||||
|
||||
- (NSMutableArray<TUIChatMediaTask *> *)findPlaceHolderListByConversationID:(NSString *)conversationID {
|
||||
NSMutableArray *tasks = [NSMutableArray arrayWithCapacity:1];
|
||||
for (TUIChatMediaTask * task in self.tasks.allValues) {
|
||||
if ([task.conversationID isEqualToString:conversationID]) {
|
||||
[tasks addObject:task];
|
||||
}
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
- (void)addCurrentVC:(UIViewController *)vc {
|
||||
[self.mediaSendingControllers addObject:vc];
|
||||
}
|
||||
|
||||
- (void)removeCurrentVC:(UIViewController *)vc {
|
||||
[self.mediaSendingControllers removeObject:vc];
|
||||
}
|
||||
|
||||
@end
|
||||
20
TUIKit/TUIChat/CommonModel/TUIChatModifyMessageHelper.h
Normal file
20
TUIKit/TUIChat/CommonModel/TUIChatModifyMessageHelper.h
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// TUIChatModifyMessageHelper.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/6/13.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@import ImSDK_Plus;
|
||||
|
||||
@interface TUIChatModifyMessageHelper : NSObject
|
||||
|
||||
+ (TUIChatModifyMessageHelper *)defaultHelper;
|
||||
|
||||
- (void)modifyMessage:(V2TIMMessage *)msg simpleCurrentContent:(NSDictionary *)simpleCurrentContent;
|
||||
|
||||
- (void)modifyMessage:(V2TIMMessage *)msg revokeMsgID:(NSString *)revokeMsgID;
|
||||
|
||||
@end
|
||||
340
TUIKit/TUIChat/CommonModel/TUIChatModifyMessageHelper.m
Normal file
340
TUIKit/TUIChat/CommonModel/TUIChatModifyMessageHelper.m
Normal file
@@ -0,0 +1,340 @@
|
||||
//
|
||||
// TUIChatModifyMessageHelper.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/6/13.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIChatModifyMessageHelper.h"
|
||||
#import <TUICore/TUILogin.h>
|
||||
#import <TUICore/TUITool.h>
|
||||
#import "TUICloudCustomDataTypeCenter.h"
|
||||
|
||||
#define RETRY_MIN_TIME 500
|
||||
#define RETRY_MAX_TIME 3000
|
||||
|
||||
#define TIMES_CONTROL 3
|
||||
|
||||
@interface TUIChatModifyMessageObj : NSObject
|
||||
|
||||
@property(nonatomic, assign) NSInteger time;
|
||||
|
||||
@property(nonatomic, copy) NSString *msgID;
|
||||
|
||||
@property(nonatomic, strong) V2TIMMessage *msg;
|
||||
// reply
|
||||
@property(nonatomic, strong) NSDictionary *simpleCurrentContent;
|
||||
// revoke
|
||||
@property(nonatomic, copy) NSString *revokeMsgID;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIChatModifyMessageObj
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
self.time = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (V2TIMMessage *)resolveOriginCloudCustomData:(V2TIMMessage *)rootMsg {
|
||||
if (self.simpleCurrentContent) {
|
||||
return [self.class resolveOriginCloudCustomData:rootMsg simpleCurrentContent:self.simpleCurrentContent];
|
||||
}
|
||||
if (self.revokeMsgID) {
|
||||
return [self.class resolveOriginCloudCustomData:rootMsg revokeMsgID:self.revokeMsgID];
|
||||
}
|
||||
return rootMsg;
|
||||
}
|
||||
|
||||
// Input
|
||||
+ (V2TIMMessage *)resolveOriginCloudCustomData:(V2TIMMessage *)rootMsg simpleCurrentContent:(NSDictionary *)simpleCurrentContent {
|
||||
NSMutableDictionary *mudic = [[NSMutableDictionary alloc] initWithCapacity:5];
|
||||
NSMutableArray *replies = [[NSMutableArray alloc] initWithCapacity:5];
|
||||
NSMutableDictionary *messageReplies = [[NSMutableDictionary alloc] initWithCapacity:5];
|
||||
|
||||
if (rootMsg.cloudCustomData) {
|
||||
NSDictionary *originDic = [TUITool jsonData2Dictionary:rootMsg.cloudCustomData];
|
||||
if (originDic && [originDic isKindOfClass:[NSDictionary class]]) {
|
||||
[messageReplies addEntriesFromDictionary:originDic];
|
||||
}
|
||||
NSArray *messageRepliesArray = originDic[@"messageReplies"][@"replies"];
|
||||
if (messageRepliesArray && [messageRepliesArray isKindOfClass:NSArray.class] && messageRepliesArray.count > 0) {
|
||||
[replies addObjectsFromArray:messageRepliesArray];
|
||||
}
|
||||
}
|
||||
[replies addObject:simpleCurrentContent];
|
||||
[mudic setValue:replies forKey:@"replies"];
|
||||
|
||||
[messageReplies setValue:mudic forKey:@"messageReplies"];
|
||||
[messageReplies setValue:@"1" forKey:@"version"];
|
||||
|
||||
NSData *data = [TUITool dictionary2JsonData:messageReplies];
|
||||
if (data) {
|
||||
rootMsg.cloudCustomData = data;
|
||||
}
|
||||
|
||||
return rootMsg;
|
||||
}
|
||||
|
||||
// revoke
|
||||
|
||||
+ (V2TIMMessage *)resolveOriginCloudCustomData:(V2TIMMessage *)rootMsg revokeMsgID:(NSString *)revokeMsgId {
|
||||
NSMutableDictionary *mudic = [[NSMutableDictionary alloc] initWithCapacity:5];
|
||||
NSMutableArray *replies = [[NSMutableArray alloc] initWithCapacity:5];
|
||||
NSMutableDictionary *messageReplies = [[NSMutableDictionary alloc] initWithCapacity:5];
|
||||
|
||||
if (rootMsg.cloudCustomData) {
|
||||
NSDictionary *originDic = [TUITool jsonData2Dictionary:rootMsg.cloudCustomData];
|
||||
if (originDic && [originDic isKindOfClass:[NSDictionary class]]) {
|
||||
[messageReplies addEntriesFromDictionary:originDic];
|
||||
if (originDic[@"messageReplies"] && [originDic[@"messageReplies"] isKindOfClass:[NSDictionary class]]) {
|
||||
NSArray *messageRepliesArray = originDic[@"messageReplies"][@"replies"];
|
||||
if (messageRepliesArray && [messageRepliesArray isKindOfClass:NSArray.class] && messageRepliesArray.count > 0) {
|
||||
[replies addObjectsFromArray:messageRepliesArray];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSMutableArray *filterReplies = [[NSMutableArray alloc] initWithCapacity:5];
|
||||
[replies enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
|
||||
if (obj && [obj isKindOfClass:NSDictionary.class]) {
|
||||
NSDictionary *dic = (NSDictionary *)obj;
|
||||
if (IS_NOT_EMPTY_NSSTRING(dic[@"messageID"])) {
|
||||
NSString *messageID = dic[@"messageID"];
|
||||
if (![messageID isEqualToString:revokeMsgId]) {
|
||||
[filterReplies addObject:dic];
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
[mudic setValue:filterReplies forKey:@"replies"];
|
||||
|
||||
[messageReplies setValue:mudic forKey:@"messageReplies"];
|
||||
[messageReplies setValue:@"1" forKey:@"version"];
|
||||
|
||||
NSData *data = [TUITool dictionary2JsonData:messageReplies];
|
||||
if (data) {
|
||||
rootMsg.cloudCustomData = data;
|
||||
}
|
||||
|
||||
return rootMsg;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface ModifyCustomOperation : NSOperation
|
||||
|
||||
@property(nonatomic, strong) TUIChatModifyMessageObj *obj;
|
||||
|
||||
@property(nonatomic, copy) void (^SuccessBlock)(void);
|
||||
|
||||
@property(nonatomic, copy) void (^FailedBlock)(int code, NSString *desc, V2TIMMessage *msg);
|
||||
|
||||
@property(nonatomic, assign) BOOL bees_executing;
|
||||
|
||||
@property(nonatomic, assign) BOOL bees_finished;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ModifyCustomOperation
|
||||
|
||||
- (void)dealloc {
|
||||
NSLog(@"operation-------dealloc");
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
if (self.isCancelled) {
|
||||
[self completeOperation];
|
||||
return;
|
||||
}
|
||||
|
||||
__block V2TIMMessage *resolveMsg = nil;
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
|
||||
self.bees_executing = YES;
|
||||
|
||||
resolveMsg = [self.obj resolveOriginCloudCustomData:self.obj.msg];
|
||||
|
||||
[[V2TIMManager sharedInstance] modifyMessage:resolveMsg
|
||||
completion:^(int code, NSString *desc, V2TIMMessage *msg) {
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
if (code != 0) {
|
||||
if (strongSelf.FailedBlock) {
|
||||
strongSelf.FailedBlock(code, desc, msg);
|
||||
}
|
||||
} else {
|
||||
if (strongSelf.SuccessBlock) {
|
||||
strongSelf.SuccessBlock();
|
||||
}
|
||||
}
|
||||
|
||||
[strongSelf completeOperation];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)completeOperation {
|
||||
self.bees_executing = NO;
|
||||
self.bees_finished = YES;
|
||||
}
|
||||
|
||||
- (void)cancel {
|
||||
[super cancel];
|
||||
[self completeOperation];
|
||||
}
|
||||
|
||||
#pragma mark - settter and getter
|
||||
- (void)setBees_executing:(BOOL)bees_executing {
|
||||
[self willChangeValueForKey:@"isExecuting"];
|
||||
_bees_executing = bees_executing;
|
||||
[self didChangeValueForKey:@"isExecuting"];
|
||||
}
|
||||
|
||||
- (void)setBees_finished:(BOOL)bees_finished {
|
||||
[self willChangeValueForKey:@"isFinished"];
|
||||
_bees_finished = bees_finished;
|
||||
[self didChangeValueForKey:@"isFinished"];
|
||||
}
|
||||
|
||||
- (BOOL)isExecuting {
|
||||
return self.bees_executing;
|
||||
}
|
||||
|
||||
- (BOOL)isFinished {
|
||||
return self.bees_finished;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIChatModifyMessageHelper () <V2TIMAdvancedMsgListener>
|
||||
|
||||
@property(nonatomic, strong) NSMutableDictionary<NSString *, TUIChatModifyMessageObj *> *modifyMessageHelperMap;
|
||||
|
||||
@property(nonatomic, strong) NSOperationQueue *queue;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIChatModifyMessageHelper
|
||||
|
||||
+ (TUIChatModifyMessageHelper *)defaultHelper {
|
||||
static dispatch_once_t onceToken;
|
||||
static TUIChatModifyMessageHelper *config;
|
||||
dispatch_once(&onceToken, ^{
|
||||
config = [[TUIChatModifyMessageHelper alloc] init];
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self registerTUIKitNotification];
|
||||
self.modifyMessageHelperMap = [NSMutableDictionary dictionaryWithCapacity:10];
|
||||
[self setupOpQueue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (void)setupOpQueue {
|
||||
self.queue = [[NSOperationQueue alloc] init];
|
||||
self.queue.maxConcurrentOperationCount = 1;
|
||||
}
|
||||
|
||||
- (void)registerTUIKitNotification {
|
||||
[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];
|
||||
}
|
||||
|
||||
- (void)onRecvMessageModified:(V2TIMMessage *)msg {
|
||||
NSString *msgID = msg.msgID;
|
||||
TUIChatModifyMessageObj *obj = self.modifyMessageHelperMap[msgID];
|
||||
if (obj && [obj isKindOfClass:[TUIChatModifyMessageObj class]]) {
|
||||
// update;
|
||||
obj.msg = msg;
|
||||
}
|
||||
}
|
||||
#pragma mark - public
|
||||
|
||||
- (void)modifyMessage:(V2TIMMessage *)msg reactEmoji:(NSString *)emojiName {
|
||||
[self modifyMessage:msg reactEmoji:nil simpleCurrentContent:nil revokeMsgID:nil timeControl:0];
|
||||
}
|
||||
|
||||
- (void)modifyMessage:(V2TIMMessage *)msg simpleCurrentContent:(NSDictionary *)simpleCurrentContent {
|
||||
[self modifyMessage:msg reactEmoji:nil simpleCurrentContent:simpleCurrentContent revokeMsgID:nil timeControl:0];
|
||||
}
|
||||
|
||||
- (void)modifyMessage:(V2TIMMessage *)msg revokeMsgID:(NSString *)revokeMsgID {
|
||||
[self modifyMessage:msg reactEmoji:nil simpleCurrentContent:nil revokeMsgID:revokeMsgID timeControl:0];
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
- (void)modifyMessage:(V2TIMMessage *)msg
|
||||
reactEmoji:(NSString *)emojiName
|
||||
simpleCurrentContent:(NSDictionary *)simpleCurrentContent
|
||||
revokeMsgID:(NSString *)revokeMsgID
|
||||
timeControl:(NSInteger)time {
|
||||
NSString *msgID = msg.msgID;
|
||||
if (!IS_NOT_EMPTY_NSSTRING(msgID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TUIChatModifyMessageObj *obj = [[TUIChatModifyMessageObj alloc] init];
|
||||
obj.msgID = msgID;
|
||||
obj.msg = msg;
|
||||
obj.time = time;
|
||||
|
||||
if (simpleCurrentContent) {
|
||||
obj.simpleCurrentContent = simpleCurrentContent;
|
||||
}
|
||||
if (revokeMsgID) {
|
||||
obj.revokeMsgID = revokeMsgID;
|
||||
}
|
||||
|
||||
[self.modifyMessageHelperMap setObject:obj forKey:obj.msgID];
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
|
||||
ModifyCustomOperation *modifyop = [[ModifyCustomOperation alloc] init];
|
||||
modifyop.obj = obj;
|
||||
modifyop.SuccessBlock = ^{
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
[strongSelf.modifyMessageHelperMap removeObjectForKey:msgID];
|
||||
};
|
||||
|
||||
modifyop.FailedBlock = ^(int code, NSString *desc, V2TIMMessage *msg) {
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
if (obj.time <= TIMES_CONTROL) {
|
||||
int delay;
|
||||
delay = [self getRandomNumber:RETRY_MIN_TIME to:RETRY_MAX_TIME];
|
||||
|
||||
TUIChatModifyMessageObj *obj = strongSelf.modifyMessageHelperMap[msgID];
|
||||
// update
|
||||
obj.msg = msg;
|
||||
obj.time = obj.time + 1;
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{
|
||||
TUIChatModifyMessageObj *obj = strongSelf.modifyMessageHelperMap[msgID];
|
||||
if (obj && [obj isKindOfClass:[TUIChatModifyMessageObj class]]) {
|
||||
[strongSelf modifyMessage:obj.msg
|
||||
reactEmoji:nil
|
||||
simpleCurrentContent:obj.simpleCurrentContent
|
||||
revokeMsgID:obj.revokeMsgID
|
||||
timeControl:obj.time];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
[strongSelf.modifyMessageHelperMap removeObjectForKey:msgID];
|
||||
}
|
||||
};
|
||||
|
||||
if (!modifyop.isCancelled) {
|
||||
[self.queue addOperation:modifyop];
|
||||
}
|
||||
}
|
||||
|
||||
- (int)getRandomNumber:(int)from to:(int)to {
|
||||
return (int)(from + (arc4random() % (to - from + 1)));
|
||||
}
|
||||
|
||||
@end
|
||||
21
TUIKit/TUIChat/CommonModel/TUICircleLodingView.h
Normal file
21
TUIKit/TUIChat/CommonModel/TUICircleLodingView.h
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// TUICircleLodingView.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2023/4/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@interface TUICircleLodingView : UIView
|
||||
@property(nonatomic, strong) UILabel *labProgress;
|
||||
@property(nonatomic, strong) CAShapeLayer *progressLayer;
|
||||
@property(nonatomic, strong) CAShapeLayer *grayProgressLayer;
|
||||
// 【0,1.0】
|
||||
@property(nonatomic, assign) double progress;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
100
TUIKit/TUIChat/CommonModel/TUICircleLodingView.m
Normal file
100
TUIKit/TUIChat/CommonModel/TUICircleLodingView.m
Normal file
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// TUICircleLodingView.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2023/4/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUICircleLodingView.h"
|
||||
|
||||
#define kCircleUnFillColor [[UIColor whiteColor] colorWithAlphaComponent:0.4]
|
||||
|
||||
#define kCircleFillColor [UIColor whiteColor]
|
||||
|
||||
@interface TUICircleLodingView ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUICircleLodingView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self drawProgressCircleWithEndAngle:-M_PI_2 + M_PI * 2 isGrayCircle:YES];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setProgress:(double)progress {
|
||||
_progress = progress;
|
||||
self.labProgress.text = [NSString stringWithFormat:@"%.0f%%", progress];
|
||||
[self drawProgress];
|
||||
}
|
||||
|
||||
- (void)drawProgress {
|
||||
if (self.progress >= 100) {
|
||||
return;
|
||||
}
|
||||
[self drawProgressCircleWithEndAngle:-M_PI_2 + M_PI * 2 * (self.progress) * 0.01 isGrayCircle:NO];
|
||||
}
|
||||
|
||||
- (void)drawProgressCircleWithEndAngle:(CGFloat)endAngle isGrayCircle:(BOOL)isGrayCircle {
|
||||
CGPoint center = CGPointMake(self.frame.size.width / 2, self.frame.size.width / 2);
|
||||
CGFloat radius = self.frame.size.width / 2;
|
||||
CGFloat startA = -M_PI_2;
|
||||
CGFloat endA = endAngle;
|
||||
|
||||
CAShapeLayer *layer;
|
||||
if (isGrayCircle) {
|
||||
layer = self.grayProgressLayer;
|
||||
} else {
|
||||
layer = self.progressLayer;
|
||||
}
|
||||
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
|
||||
layer.path = [path CGPath];
|
||||
}
|
||||
|
||||
- (UILabel *)labProgress {
|
||||
if (!_labProgress) {
|
||||
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
|
||||
label.textAlignment = NSTextAlignmentCenter;
|
||||
label.center = CGPointMake(self.frame.size.width / 2, self.frame.size.width / 2);
|
||||
label.textColor = kCircleFillColor;
|
||||
label.font = [UIFont systemFontOfSize:10];
|
||||
[self addSubview:label];
|
||||
_labProgress = label;
|
||||
}
|
||||
return _labProgress;
|
||||
}
|
||||
|
||||
- (CAShapeLayer *)grayProgressLayer {
|
||||
if (!_grayProgressLayer) {
|
||||
_grayProgressLayer = [CAShapeLayer layer];
|
||||
_grayProgressLayer.frame = self.bounds;
|
||||
_grayProgressLayer.fillColor = [[UIColor clearColor] CGColor];
|
||||
_grayProgressLayer.strokeColor = kCircleUnFillColor.CGColor;
|
||||
_grayProgressLayer.opacity = 1;
|
||||
_grayProgressLayer.lineCap = kCALineCapRound;
|
||||
_grayProgressLayer.lineWidth = 3;
|
||||
[self.layer addSublayer:_grayProgressLayer];
|
||||
}
|
||||
return _grayProgressLayer;
|
||||
}
|
||||
|
||||
- (CAShapeLayer *)progressLayer {
|
||||
if (!_progressLayer) {
|
||||
_progressLayer = [CAShapeLayer layer];
|
||||
_progressLayer.frame = self.bounds;
|
||||
_progressLayer.fillColor = [[UIColor clearColor] CGColor];
|
||||
_progressLayer.strokeColor = kCircleFillColor.CGColor;
|
||||
_progressLayer.opacity = 1;
|
||||
_progressLayer.lineCap = kCALineCapRound;
|
||||
_progressLayer.lineWidth = 3;
|
||||
[self.layer addSublayer:_progressLayer];
|
||||
}
|
||||
return _progressLayer;
|
||||
}
|
||||
|
||||
@end
|
||||
53
TUIKit/TUIChat/CommonModel/TUICloudCustomDataTypeCenter.h
Normal file
53
TUIKit/TUIChat/CommonModel/TUICloudCustomDataTypeCenter.h
Normal file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// TUICloudCustomDataTypeCenter.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/4/29.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <ImSDK_Plus/ImSDK_Plus.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_OPTIONS(NSUInteger, TUICloudCustomDataType) {
|
||||
TUICloudCustomDataType_None = 1 << 0,
|
||||
TUICloudCustomDataType_MessageReply = 1 << 1,
|
||||
// TUICloudCustomDataType_MessageReact = 1 << 2,
|
||||
TUICloudCustomDataType_MessageReplies = 1 << 3,
|
||||
TUICloudCustomDataType_MessageReference = 1 << 4,
|
||||
};
|
||||
|
||||
typedef NSString *TUICustomType;
|
||||
|
||||
FOUNDATION_EXTERN TUICustomType messageFeature;
|
||||
|
||||
@interface V2TIMMessage (CloudCustomDataType)
|
||||
|
||||
- (void)doThingsInContainsCloudCustomOfDataType:(TUICloudCustomDataType)type callback:(void (^)(BOOL isContains, id obj))callback;
|
||||
/**
|
||||
* Whether this state is included
|
||||
*/
|
||||
- (BOOL)isContainsCloudCustomOfDataType:(TUICloudCustomDataType)type;
|
||||
|
||||
/**
|
||||
* Parse data of specified type
|
||||
* The return value is: data of type NSDictionary/NSArray/NSString/NSNumber
|
||||
*/
|
||||
- (NSObject *)parseCloudCustomData:(TUICustomType)customType;
|
||||
|
||||
/**
|
||||
* Set the specified type of data
|
||||
* jsonData: NSDictionary/NSArray/NSString/NSNumber type data, which can be directly converted to json
|
||||
*/
|
||||
- (void)setCloudCustomData:(NSObject *)jsonData forType:(TUICustomType)customType;
|
||||
|
||||
- (void)modifyIfNeeded:(V2TIMMessageModifyCompletion)callback;
|
||||
|
||||
@end
|
||||
@interface TUICloudCustomDataTypeCenter : NSObject
|
||||
+ (NSString *)convertType2String:(TUICloudCustomDataType)type;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
250
TUIKit/TUIChat/CommonModel/TUICloudCustomDataTypeCenter.m
Normal file
250
TUIKit/TUIChat/CommonModel/TUICloudCustomDataTypeCenter.m
Normal file
@@ -0,0 +1,250 @@
|
||||
//
|
||||
// TUICloudCustomDataTypeCenter.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/4/29.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUICloudCustomDataTypeCenter.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
TUICustomType messageFeature = @"messageFeature";
|
||||
|
||||
@implementation V2TIMMessage (CloudCustomDataType)
|
||||
|
||||
- (BOOL)hasAnyCloudCustomDataType {
|
||||
if (self.cloudCustomData == nil) {
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)doThingsInContainsCloudCustomOfDataType:(TUICloudCustomDataType)type callback:(void (^)(BOOL isContains, id obj))callback {
|
||||
if (self.cloudCustomData == nil) {
|
||||
if (callback) {
|
||||
callback(NO, nil);
|
||||
}
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:self.cloudCustomData options:0 error:&error];
|
||||
NSString *typeStr = [TUICloudCustomDataTypeCenter convertType2String:type];
|
||||
|
||||
if (![typeStr isKindOfClass:[NSString class]] || typeStr.length <= 0) {
|
||||
if (callback) {
|
||||
callback(NO, nil);
|
||||
}
|
||||
}
|
||||
if (error || dict == nil || ![dict isKindOfClass:NSDictionary.class] || ![dict.allKeys containsObject:typeStr]) {
|
||||
if (callback) {
|
||||
callback(NO, nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// extra condition
|
||||
if (type == TUICloudCustomDataType_MessageReply) {
|
||||
NSDictionary *reply = [dict valueForKey:typeStr];
|
||||
if (reply == nil || ![reply isKindOfClass:NSDictionary.class]) {
|
||||
if (callback) {
|
||||
callback(NO, nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (![reply.allKeys containsObject:@"version"] || [reply[@"version"] intValue] > kMessageReplyVersion) {
|
||||
NSLog(@"not match the version of message rely");
|
||||
if (callback) {
|
||||
callback(NO, nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (callback) {
|
||||
callback(YES, reply);
|
||||
}
|
||||
}
|
||||
|
||||
if (type == TUICloudCustomDataType_MessageReference) {
|
||||
NSDictionary *reply = [dict valueForKey:typeStr];
|
||||
if (reply == nil || ![reply isKindOfClass:NSDictionary.class]) {
|
||||
if (callback) {
|
||||
callback(NO, nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (![reply.allKeys containsObject:@"version"] || [reply[@"version"] intValue] > kMessageReplyVersion) {
|
||||
NSLog(@"not match the version of message rely");
|
||||
if (callback) {
|
||||
callback(NO, nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ([reply.allKeys containsObject:@"messageRootID"]) {
|
||||
if (callback) {
|
||||
callback(NO, nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (callback) {
|
||||
callback(YES, reply);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == TUICloudCustomDataType_MessageReplies) {
|
||||
NSDictionary *messageReplies = [dict valueForKey:typeStr];
|
||||
NSArray *reply = [messageReplies valueForKey:@"replies"];
|
||||
if (reply == nil || ![reply isKindOfClass:NSArray.class]) {
|
||||
if (callback) {
|
||||
callback(NO, nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (reply.count <= 0) {
|
||||
if (callback) {
|
||||
callback(NO, nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(YES, dict);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
- (BOOL)isContainsCloudCustomOfDataType:(TUICloudCustomDataType)type {
|
||||
if (self.cloudCustomData == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:self.cloudCustomData options:0 error:&error];
|
||||
NSString *typeStr = [TUICloudCustomDataTypeCenter convertType2String:type];
|
||||
// NSDictionary *customElemDic = [NSJSONSerialization JSONObjectWithData:self.customElem.data options:0 error:&error];
|
||||
|
||||
if (![typeStr isKindOfClass:[NSString class]] || typeStr.length <= 0) {
|
||||
return NO;
|
||||
}
|
||||
if (error || dict == nil || ![dict isKindOfClass:NSDictionary.class] || ![dict.allKeys containsObject:typeStr]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// extra condition
|
||||
if (type == TUICloudCustomDataType_MessageReply) {
|
||||
NSDictionary *reply = [dict valueForKey:typeStr];
|
||||
if (reply == nil || ![reply isKindOfClass:NSDictionary.class]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (![reply.allKeys containsObject:@"version"] || [reply[@"version"] intValue] > kMessageReplyVersion) {
|
||||
NSLog(@"not match the version of message rely");
|
||||
return NO;
|
||||
}
|
||||
if (![reply.allKeys containsObject:@"messageRootID"]) {
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
if (type == TUICloudCustomDataType_MessageReference) {
|
||||
NSDictionary *reply = [dict valueForKey:typeStr];
|
||||
if (reply == nil || ![reply isKindOfClass:NSDictionary.class]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (![reply.allKeys containsObject:@"version"] || [reply[@"version"] intValue] > kMessageReplyVersion) {
|
||||
NSLog(@"not match the version of message rely");
|
||||
return NO;
|
||||
}
|
||||
if ([reply.allKeys containsObject:@"messageRootID"]) {
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (type == TUICloudCustomDataType_MessageReplies) {
|
||||
NSDictionary *messageReplies = [dict valueForKey:typeStr];
|
||||
NSArray *reply = [messageReplies valueForKey:@"replies"];
|
||||
if (reply == nil || ![reply isKindOfClass:NSArray.class]) {
|
||||
return NO;
|
||||
}
|
||||
if (reply.count <= 0) {
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSObject *)parseCloudCustomData:(TUICustomType)customType {
|
||||
if (self.cloudCustomData == nil || customType.length == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:self.cloudCustomData options:0 error:&error];
|
||||
|
||||
if (error || dict == nil || ![dict isKindOfClass:NSDictionary.class] || ![dict.allKeys containsObject:customType]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [dict objectForKey:customType];
|
||||
}
|
||||
|
||||
- (void)setCloudCustomData:(NSObject *)jsonData forType:(TUICustomType)customType {
|
||||
if (jsonData == nil || customType.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *dict = @{};
|
||||
|
||||
if (self.cloudCustomData) {
|
||||
dict = [NSJSONSerialization JSONObjectWithData:self.cloudCustomData options:0 error:nil];
|
||||
if (dict == nil) {
|
||||
dict = @{};
|
||||
}
|
||||
}
|
||||
|
||||
if (![dict isKindOfClass:NSDictionary.class]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary *originDataDict = [NSMutableDictionary dictionaryWithDictionary:dict];
|
||||
if ([originDataDict.allKeys containsObject:customType]) {
|
||||
[originDataDict removeObjectForKey:customType];
|
||||
}
|
||||
[originDataDict setObject:jsonData forKey:customType];
|
||||
|
||||
NSData *data = [NSJSONSerialization dataWithJSONObject:originDataDict options:0 error:nil];
|
||||
if (data) {
|
||||
self.cloudCustomData = data;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)modifyIfNeeded:(V2TIMMessageModifyCompletion)callback {
|
||||
[V2TIMManager.sharedInstance modifyMessage:self completion:callback];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUICloudCustomDataTypeCenter
|
||||
|
||||
+ (NSString *)convertType2String:(TUICloudCustomDataType)type {
|
||||
NSString *resultString = @"";
|
||||
switch (type) {
|
||||
case TUICloudCustomDataType_MessageReply:
|
||||
case TUICloudCustomDataType_MessageReference:
|
||||
resultString = @"messageReply";
|
||||
break;
|
||||
case TUICloudCustomDataType_MessageReplies:
|
||||
resultString = @"messageReplies";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return resultString;
|
||||
}
|
||||
@end
|
||||
45
TUIKit/TUIChat/CommonModel/TUIEmojiConfig.h
Normal file
45
TUIKit/TUIChat/CommonModel/TUIEmojiConfig.h
Normal file
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// TUIEmojiConfig.h
|
||||
// TUIEmojiPlugin
|
||||
//
|
||||
// Created by wyl on 2023/11/13.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@class TUIFaceGroup;
|
||||
|
||||
@interface TUIEmojiConfig : NSObject
|
||||
+ (TUIEmojiConfig *)defaultConfig;
|
||||
|
||||
/**
|
||||
* In respect for the copyright of the emoji design, the Chat Demo/TUIKit project does not include the cutouts of large emoji elements. Please replace them
|
||||
* with your own designed or copyrighted emoji packs before the official launch for commercial use. The default small yellow face emoji pack is copyrighted by
|
||||
* Tencent Cloud and can be authorized for a fee. If you wish to obtain authorization, please submit a ticket to contact us.
|
||||
*
|
||||
* submit a ticket url:https://console.cloud.tencent.com/workorder/category?level1_id=29&level2_id=40&source=14&data_title=%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%20IM&step=1 (China mainland)
|
||||
* submit a ticket url:https://console.tencentcloud.com/workorder/category?level1_id=29&level2_id=40&source=14&data_title=Chat&step=1 (Other regions)
|
||||
*/
|
||||
@property(nonatomic, strong) NSArray<TUIFaceGroup *> *faceGroups;
|
||||
|
||||
/**
|
||||
* The list of emoticons displayed after long-pressing the message on the chat interface
|
||||
*/
|
||||
@property(nonatomic, strong) NSArray<TUIFaceGroup *> *chatPopDetailGroups;
|
||||
|
||||
@property(nonatomic, strong) NSArray<TUIFaceGroup *> *chatContextEmojiDetailGroups;
|
||||
|
||||
- (void)appendFaceGroup:(TUIFaceGroup *)faceGroup;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIEmojiConfig (defaultFace)
|
||||
- (TUIFaceGroup *)getChatPopMenuRecentQueue;
|
||||
- (void)updateEmojiGroups;
|
||||
- (void)updateRecentMenuQueue:(NSString *)faceName;
|
||||
@end
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
235
TUIKit/TUIChat/CommonModel/TUIEmojiConfig.m
Normal file
235
TUIKit/TUIChat/CommonModel/TUIEmojiConfig.m
Normal file
@@ -0,0 +1,235 @@
|
||||
//
|
||||
// TUIEmojiConfig.m
|
||||
// TUIEmojiPlugin
|
||||
//
|
||||
// Created by wyl on 2023/11/13.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIEmojiConfig.h"
|
||||
typedef NS_ENUM(NSUInteger, TUIEmojiFaceType) {
|
||||
TUIEmojiFaceTypeKeyBoard = 0,
|
||||
TUIEmojiFaceTypePopDetail = 1,
|
||||
TUIEmojiFaceTypePopContextDetail = 2,
|
||||
};
|
||||
|
||||
@interface TUIEmojiConfig ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIEmojiConfig
|
||||
|
||||
+ (void)load {
|
||||
NSLog(@"TUIEmojiConfig load%@",[TUIEmojiConfig defaultConfig]);
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self updateEmojiGroups];
|
||||
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(onChangeLanguage) name:TUIChangeLanguageNotification object:nil];
|
||||
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(onChangeTheme) name:TUIDidApplyingThemeChangedNotfication object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (id)defaultConfig {
|
||||
static dispatch_once_t onceToken;
|
||||
static TUIEmojiConfig *config;
|
||||
dispatch_once(&onceToken, ^{
|
||||
config = [[TUIEmojiConfig alloc] init];
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
- (void)appendFaceGroup:(TUIFaceGroup *)faceGroup {
|
||||
NSMutableArray *faceGroupMenu = [NSMutableArray arrayWithArray:self.faceGroups];
|
||||
[faceGroupMenu addObject:faceGroup];
|
||||
self.faceGroups = faceGroupMenu;
|
||||
}
|
||||
|
||||
- (void)onChangeLanguage {
|
||||
[self updateEmojiGroups];
|
||||
}
|
||||
|
||||
- (void)onChangeTheme { }
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIEmojiConfig (defaultFace)
|
||||
|
||||
|
||||
- (void)updateEmojiGroups {
|
||||
self.faceGroups = [self updateFaceGroups:self.faceGroups type:TUIEmojiFaceTypeKeyBoard];
|
||||
self.chatPopDetailGroups = [self updateFaceGroups:self.chatPopDetailGroups type:TUIEmojiFaceTypePopDetail];
|
||||
self.chatContextEmojiDetailGroups = [self updateFaceGroups:self.chatContextEmojiDetailGroups type:TUIEmojiFaceTypePopContextDetail];
|
||||
}
|
||||
|
||||
|
||||
- (NSArray *)updateFaceGroups:(NSArray *)groups type:(TUIEmojiFaceType)type {
|
||||
if (groups.count) {
|
||||
NSMutableArray *arrayM = [NSMutableArray arrayWithArray:groups];
|
||||
[arrayM removeObjectAtIndex:0];
|
||||
|
||||
TUIFaceGroup *defaultFaceGroup = [self findFaceGroupAboutType:type];
|
||||
if (defaultFaceGroup) {
|
||||
[arrayM insertObject:[self findFaceGroupAboutType:type] atIndex:0];
|
||||
}
|
||||
return [NSArray arrayWithArray:arrayM];
|
||||
} else {
|
||||
NSMutableArray *faceArray = [NSMutableArray array];
|
||||
TUIFaceGroup *defaultFaceGroup = [self findFaceGroupAboutType:type];
|
||||
if (defaultFaceGroup) {
|
||||
[faceArray addObject:defaultFaceGroup];
|
||||
}
|
||||
return faceArray;
|
||||
}
|
||||
return @[];
|
||||
}
|
||||
- (TUIFaceGroup *)findFaceGroupAboutType:(TUIEmojiFaceType)type {
|
||||
// emoji group
|
||||
|
||||
NSMutableArray *emojiFaces = [NSMutableArray array];
|
||||
NSArray *emojis = [NSArray arrayWithContentsOfFile:TUIChatFaceImagePath(@"emoji/emoji.plist")];
|
||||
for (NSDictionary *dic in emojis) {
|
||||
TUIFaceCellData *data = [[TUIFaceCellData alloc] init];
|
||||
NSString *name = [dic objectForKey:@"face_name"];
|
||||
NSString *fileName = [dic objectForKey:@"face_file"];
|
||||
NSString *path = [NSString stringWithFormat:@"emoji/%@", fileName];
|
||||
NSString *localizableName = [TUIGlobalization getLocalizedStringForKey:name bundle:@"TUIChatFace"];
|
||||
data.name = name;
|
||||
data.path = TUIChatFaceImagePath(path);
|
||||
data.localizableName = localizableName;
|
||||
[self addFaceToCache:data.path];
|
||||
[emojiFaces addObject:data];
|
||||
}
|
||||
if (emojiFaces.count != 0) {
|
||||
TUIFaceGroup *emojiGroup = [[TUIFaceGroup alloc] init];
|
||||
emojiGroup.faces = emojiFaces;
|
||||
emojiGroup.groupIndex = 0;
|
||||
emojiGroup.groupPath = TUIChatFaceImagePath(@"emoji/");
|
||||
emojiGroup.menuPath = TUIChatFaceImagePath(@"emoji/menu");
|
||||
emojiGroup.isNeedAddInInputBar = YES;
|
||||
emojiGroup.groupName = TIMCommonLocalizableString(TUIChatFaceGroupAllEmojiName);
|
||||
if (type == TUIEmojiFaceTypeKeyBoard) {
|
||||
emojiGroup.rowCount = 4;
|
||||
emojiGroup.itemCountPerRow = 8;
|
||||
emojiGroup.needBackDelete = NO;
|
||||
} else if (type == TUIEmojiFaceTypePopDetail) {
|
||||
emojiGroup.rowCount = 3;
|
||||
emojiGroup.itemCountPerRow = 8;
|
||||
emojiGroup.needBackDelete = NO;
|
||||
}
|
||||
else if (type == TUIEmojiFaceTypePopContextDetail) {
|
||||
emojiGroup.rowCount = 20;
|
||||
emojiGroup.itemCountPerRow = 7;
|
||||
emojiGroup.needBackDelete = NO;
|
||||
}
|
||||
|
||||
[self addFaceToCache:emojiGroup.menuPath];
|
||||
[self addFaceToCache:TUIChatFaceImagePath(@"del_normal")];
|
||||
[self addFaceToCache:TUIChatFaceImagePath(@"ic_unknown_image")];
|
||||
return emojiGroup;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - chatPopMenuQueue
|
||||
- (NSArray *)getChatPopMenuQueue {
|
||||
NSArray *emojis = [[NSUserDefaults standardUserDefaults] objectForKey:@"TUIChatPopMenuQueue"];
|
||||
if (emojis && [emojis isKindOfClass:[NSArray class]]) {
|
||||
if (emojis.count > 0) {
|
||||
//Randomly check whether an emoticon matches the current emoticon resource package
|
||||
//to avoid overwriting the installation context emoticon inconsistency.
|
||||
NSDictionary *dic = emojis.lastObject;
|
||||
NSString *name = [dic objectForKey:@"face_name"];
|
||||
NSString *fileName = [dic objectForKey:@"face_file"];
|
||||
NSString *path = [NSString stringWithFormat:@"emoji/%@", fileName];
|
||||
UIImage * image = [UIImage imageWithContentsOfFile:TUIChatFaceImagePath(path)];
|
||||
if (image) {
|
||||
return emojis;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [NSArray arrayWithContentsOfFile:TUIChatFaceImagePath(@"emoji/emojiRecentDefaultList.plist")];
|
||||
}
|
||||
|
||||
- (TUIFaceGroup *)getChatPopMenuRecentQueue {
|
||||
// emoji group
|
||||
NSMutableArray *emojiFaces = [NSMutableArray array];
|
||||
NSArray *emojis = [self getChatPopMenuQueue];
|
||||
for (NSDictionary *dic in emojis) {
|
||||
TUIFaceCellData *data = [[TUIFaceCellData alloc] init];
|
||||
NSString *name = [dic objectForKey:@"face_name"];
|
||||
NSString *fileName = [dic objectForKey:@"face_file"];
|
||||
NSString *path = [NSString stringWithFormat:@"emoji/%@", fileName];
|
||||
NSString *localizableName = [TUIGlobalization g_localizedStringForKey:name bundle:@"TUIChatFace"];
|
||||
data.name = name;
|
||||
data.path = TUIChatFaceImagePath(path);
|
||||
data.localizableName = localizableName;
|
||||
[emojiFaces addObject:data];
|
||||
}
|
||||
if (emojiFaces.count != 0) {
|
||||
TUIFaceGroup *emojiGroup = [[TUIFaceGroup alloc] init];
|
||||
emojiGroup.faces = emojiFaces;
|
||||
emojiGroup.groupIndex = 0;
|
||||
emojiGroup.groupPath = TUIChatFaceImagePath(@"emoji/");
|
||||
emojiGroup.menuPath = TUIChatFaceImagePath(@"emoji/menu");
|
||||
emojiGroup.rowCount = 1;
|
||||
emojiGroup.itemCountPerRow = 6;
|
||||
emojiGroup.needBackDelete = NO;
|
||||
emojiGroup.isNeedAddInInputBar = YES;
|
||||
return emojiGroup;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)updateRecentMenuQueue:(NSString *)faceName {
|
||||
NSArray *emojis = [self getChatPopMenuQueue];
|
||||
NSMutableArray *muArray = [NSMutableArray arrayWithArray:emojis];
|
||||
|
||||
BOOL hasInQueue = NO;
|
||||
int index = 0;
|
||||
for (NSDictionary *dic in emojis) {
|
||||
NSString *name = [dic objectForKey:@"face_name"];
|
||||
if ([name isEqualToString:faceName]) {
|
||||
hasInQueue = YES;
|
||||
break;
|
||||
}
|
||||
index ++;
|
||||
}
|
||||
if (hasInQueue) {
|
||||
NSDictionary *targetDic = emojis[index];
|
||||
[muArray removeObjectAtIndex:index];
|
||||
[muArray insertObject:targetDic atIndex:0];
|
||||
}else {
|
||||
[muArray removeLastObject];
|
||||
NSArray *emojis = [NSArray arrayWithContentsOfFile:TUIChatFaceImagePath(@"emoji/emoji.plist")];
|
||||
NSDictionary *targetDic = @{@"face_name" : faceName};
|
||||
for (NSDictionary *dic in emojis) {
|
||||
NSString *name = [dic objectForKey:@"face_name"];
|
||||
if ([name isEqualToString:faceName]) {
|
||||
targetDic = dic;
|
||||
break;
|
||||
}
|
||||
}
|
||||
[muArray insertObject:targetDic atIndex:0];
|
||||
}
|
||||
[[NSUserDefaults standardUserDefaults] setObject:muArray forKey:@"TUIChatPopMenuQueue"];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
}
|
||||
|
||||
#pragma mark - resource
|
||||
|
||||
- (void)addResourceToCache:(NSString *)path {
|
||||
[[TUIImageCache sharedInstance] addResourceToCache:path];
|
||||
}
|
||||
|
||||
- (void)addFaceToCache:(NSString *)path {
|
||||
[[TUIImageCache sharedInstance] addFaceToCache:path];
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
18
TUIKit/TUIChat/CommonModel/TUIEmojiMeditorProtocolProvider.h
Normal file
18
TUIKit/TUIChat/CommonModel/TUIEmojiMeditorProtocolProvider.h
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// TUIEmojiMeditorProtocolProvider.h
|
||||
// TUIEmojiPlugin
|
||||
//
|
||||
// Created by wyl on 2023/11/14.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <TIMCommon/TUIEmojiMeditorProtocol.h>
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@class TUIFaceGroup;
|
||||
@interface TUIEmojiMeditorProtocolProvider : NSObject <TUIEmojiMeditorProtocol>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
46
TUIKit/TUIChat/CommonModel/TUIEmojiMeditorProtocolProvider.m
Normal file
46
TUIKit/TUIChat/CommonModel/TUIEmojiMeditorProtocolProvider.m
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// TUIEmojiMeditorProtocolProvider.m
|
||||
// TUIEmojiPlugin
|
||||
//
|
||||
// Created by wyl on 2023/11/14.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIEmojiMeditorProtocolProvider.h"
|
||||
#import <TIMCommon/TUIEmojiMeditorProtocol.h>
|
||||
#import <TIMCommon/TIMCommonMediator.h>
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import "TUIEmojiConfig.h"
|
||||
|
||||
@implementation TUIEmojiMeditorProtocolProvider
|
||||
+ (void)load {
|
||||
[TIMCommonMediator.share registerService:@protocol(TUIEmojiMeditorProtocol) class:self];
|
||||
}
|
||||
|
||||
- (id)getFaceGroup {
|
||||
return [TUIEmojiConfig.defaultConfig faceGroups];
|
||||
}
|
||||
- (void)appendFaceGroup:(TUIFaceGroup *)faceGroup {
|
||||
[TUIEmojiConfig.defaultConfig appendFaceGroup:faceGroup];
|
||||
}
|
||||
|
||||
- (id)getChatPopDetailGroups {
|
||||
return [TUIEmojiConfig.defaultConfig chatPopDetailGroups];
|
||||
}
|
||||
|
||||
- (id)getChatContextEmojiDetailGroups {
|
||||
return [TUIEmojiConfig.defaultConfig chatContextEmojiDetailGroups];
|
||||
}
|
||||
|
||||
- (id)getChatPopMenuRecentQueue {
|
||||
return [TUIEmojiConfig.defaultConfig getChatPopMenuRecentQueue];
|
||||
}
|
||||
|
||||
- (void)updateRecentMenuQueue:(NSString *)faceName {
|
||||
[TUIEmojiConfig.defaultConfig updateRecentMenuQueue:faceName];
|
||||
}
|
||||
|
||||
- (void)updateEmojiGroups {
|
||||
[TUIEmojiConfig.defaultConfig updateEmojiGroups];
|
||||
}
|
||||
@end
|
||||
42
TUIKit/TUIChat/CommonModel/TUIGroupConfig.h
Normal file
42
TUIKit/TUIChat/CommonModel/TUIGroupConfig.h
Normal file
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// TUIGroupConfig.h
|
||||
// TUIGroup
|
||||
//
|
||||
// Created by Tencent on 2024/9/6.
|
||||
// Copyright © 2024 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_OPTIONS(NSInteger, TUIGroupConfigItem) {
|
||||
TUIGroupConfigItem_None = 0,
|
||||
TUIGroupConfigItem_Members = 1 << 0,
|
||||
TUIGroupConfigItem_Notice = 1 << 1,
|
||||
TUIGroupConfigItem_Manage = 1 << 2,
|
||||
TUIGroupConfigItem_Alias = 1 << 3,
|
||||
TUIGroupConfigItem_MuteAndPin = 1 << 4,
|
||||
TUIGroupConfigItem_Background = 1 << 5,
|
||||
TUIGroupConfigItem_ClearChatHistory = 1 << 6,
|
||||
TUIGroupConfigItem_DeleteAndLeave = 1 << 7,
|
||||
TUIGroupConfigItem_Transfer = 1 << 8,
|
||||
TUIGroupConfigItem_Dismiss = 1 << 9,
|
||||
TUIGroupConfigItem_Report = 1 << 10,
|
||||
};
|
||||
|
||||
@interface TUIGroupConfig : NSObject
|
||||
|
||||
+ (TUIGroupConfig *)sharedConfig;
|
||||
/**
|
||||
* Hide items in group config interface.
|
||||
*/
|
||||
- (void)hideItemsInGroupConfig:(TUIGroupConfigItem)items;
|
||||
/**
|
||||
* Get the hidden status of specified item.
|
||||
*/
|
||||
- (BOOL)isItemHiddenInGroupConfig:(TUIGroupConfigItem)item;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
75
TUIKit/TUIChat/CommonModel/TUIGroupConfig.m
Normal file
75
TUIKit/TUIChat/CommonModel/TUIGroupConfig.m
Normal file
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// TUIGroupConfig.m
|
||||
// TUIGroup
|
||||
//
|
||||
// Created by Tencent on 2024/9/6.
|
||||
// Copyright © 2024 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIGroupConfig.h"
|
||||
|
||||
@interface TUIGroupConfig()
|
||||
@property (nonatomic, assign) BOOL hideGroupMembersItems;
|
||||
@property (nonatomic, assign) BOOL hideGroupNoticeItem;
|
||||
@property (nonatomic, assign) BOOL hideGroupManageItems;
|
||||
@property (nonatomic, assign) BOOL hideGroupAliasItem;
|
||||
@property (nonatomic, assign) BOOL hideGroupMuteAndPinItems;
|
||||
@property (nonatomic, assign) BOOL hideGroupBackgroundItem;
|
||||
@property (nonatomic, assign) BOOL hideGroupClearChatHistory;
|
||||
@property (nonatomic, assign) BOOL hideGroupDeleteAndLeave;
|
||||
@property (nonatomic, assign) BOOL hideGroupTransfer;
|
||||
@property (nonatomic, assign) BOOL hideGroupDismiss;
|
||||
@property (nonatomic, assign) BOOL hideGroupReport;
|
||||
@end
|
||||
|
||||
@implementation TUIGroupConfig
|
||||
|
||||
+ (TUIGroupConfig *)sharedConfig {
|
||||
static dispatch_once_t onceToken;
|
||||
static TUIGroupConfig *config;
|
||||
dispatch_once(&onceToken, ^{
|
||||
config = [[TUIGroupConfig alloc] init];
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
- (void)hideItemsInGroupConfig:(TUIGroupConfigItem)items {
|
||||
self.hideGroupMuteAndPinItems = items & TUIGroupConfigItem_MuteAndPin;
|
||||
self.hideGroupManageItems = items & TUIGroupConfigItem_Manage;
|
||||
self.hideGroupAliasItem = items & TUIGroupConfigItem_Alias;
|
||||
self.hideGroupBackgroundItem = items & TUIGroupConfigItem_Background;
|
||||
self.hideGroupMembersItems = items & TUIGroupConfigItem_Members;
|
||||
self.hideGroupClearChatHistory = items & TUIGroupConfigItem_ClearChatHistory;
|
||||
self.hideGroupDeleteAndLeave = items & TUIGroupConfigItem_DeleteAndLeave;
|
||||
self.hideGroupTransfer = items & TUIGroupConfigItem_Transfer;
|
||||
self.hideGroupDismiss = items & TUIGroupConfigItem_Dismiss;
|
||||
self.hideGroupReport = items & TUIGroupConfigItem_Report;
|
||||
}
|
||||
|
||||
- (BOOL)isItemHiddenInGroupConfig:(TUIGroupConfigItem)item {
|
||||
if (item & TUIGroupConfigItem_MuteAndPin) {
|
||||
return self.hideGroupMuteAndPinItems;
|
||||
} else if (item & TUIGroupConfigItem_Manage) {
|
||||
return self.hideGroupManageItems;
|
||||
} else if (item & TUIGroupConfigItem_Alias) {
|
||||
return self.hideGroupAliasItem;
|
||||
} else if (item & TUIGroupConfigItem_Background) {
|
||||
return self.hideGroupBackgroundItem;
|
||||
} else if (item & TUIGroupConfigItem_Members) {
|
||||
return self.hideGroupMembersItems;
|
||||
} else if (item & TUIGroupConfigItem_ClearChatHistory) {
|
||||
return self.hideGroupClearChatHistory;
|
||||
} else if (item & TUIGroupConfigItem_DeleteAndLeave) {
|
||||
return self.hideGroupDeleteAndLeave;
|
||||
} else if (item & TUIGroupConfigItem_Transfer) {
|
||||
return self.hideGroupTransfer;
|
||||
} else if (item & TUIGroupConfigItem_Dismiss) {
|
||||
return self.hideGroupDismiss;
|
||||
} else if (item & TUIGroupConfigItem_Report) {
|
||||
return self.hideGroupReport;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
93
TUIKit/TUIChat/CommonModel/TUIMessageItem.h
Normal file
93
TUIKit/TUIChat/CommonModel/TUIMessageItem.h
Normal file
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// TUIChatConfig.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/6/10.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, TUIImageType) {
|
||||
TImage_Type_Origin = 1,
|
||||
TImage_Type_Thumb = 2,
|
||||
TImage_Type_Large = 4,
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIImageItem
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
@interface TUIImageItem : NSObject
|
||||
|
||||
/**
|
||||
* The inner ID for the image, can be used for external cache key
|
||||
*/
|
||||
@property(nonatomic, strong) NSString *uuid;
|
||||
|
||||
@property(nonatomic, strong) NSString *url;
|
||||
|
||||
@property(nonatomic, assign) CGSize size;
|
||||
|
||||
@property(nonatomic, assign) TUIImageType type;
|
||||
@end
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIVideoItem
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
@interface TUIVideoItem : NSObject
|
||||
|
||||
/**
|
||||
* The internal ID of the video message, which does not need to be set, is obtained from the video instance pulled by the SDK.
|
||||
*/
|
||||
@property(nonatomic, strong) NSString *uuid;
|
||||
|
||||
/**
|
||||
* The video type - the suffix of the video file - is set when sending a message. For example "mp4".
|
||||
*/
|
||||
@property(nonatomic, strong) NSString *type;
|
||||
|
||||
/**
|
||||
* The video size, no need to set, is obtained from the instance pulled by the SDK.
|
||||
*/
|
||||
@property(nonatomic, assign) NSInteger length;
|
||||
|
||||
/**
|
||||
* video duration
|
||||
*/
|
||||
@property(nonatomic, assign) NSInteger duration;
|
||||
@end
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUISnapshotItem
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface TUISnapshotItem : NSObject
|
||||
|
||||
/**
|
||||
* Image ID, internal identifier, can be used for external cache key
|
||||
*/
|
||||
@property(nonatomic, strong) NSString *uuid;
|
||||
|
||||
/**
|
||||
* Cover image type
|
||||
*/
|
||||
@property(nonatomic, strong) NSString *type;
|
||||
|
||||
/**
|
||||
* The size of the cover on the UI.
|
||||
*/
|
||||
@property(nonatomic, assign) CGSize size;
|
||||
|
||||
@property(nonatomic, assign) NSInteger length;
|
||||
@end
|
||||
NS_ASSUME_NONNULL_END
|
||||
19
TUIKit/TUIChat/CommonModel/TUIMessageItem.m
Normal file
19
TUIKit/TUIChat/CommonModel/TUIMessageItem.m
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// TUIChatConfig.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/6/10.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMessageItem.h"
|
||||
|
||||
@implementation TUIImageItem
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIVideoItem
|
||||
@end
|
||||
|
||||
@implementation TUISnapshotItem
|
||||
@end
|
||||
39
TUIKit/TUIChat/CommonModel/TUIMessageProgressManager.h
Normal file
39
TUIKit/TUIChat/CommonModel/TUIMessageProgressManager.h
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// TUIMessageProgressManager.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2022/1/4.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, TUIMessageSendingResultType) { TUIMessageSendingResultTypeSucc = 0, TUIMessageSendingResultTypeFail = 1 };
|
||||
|
||||
@protocol TUIMessageProgressManagerDelegate <NSObject>
|
||||
|
||||
- (void)onUploadProgress:(NSString *)msgID progress:(NSInteger)progress;
|
||||
- (void)onDownloadProgress:(NSString *)msgID progress:(NSInteger)progress;
|
||||
|
||||
- (void)onMessageSendingResultChanged:(TUIMessageSendingResultType)type messageID:(NSString *)msgID;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIMessageProgressManager : NSObject
|
||||
|
||||
+ (instancetype)shareManager;
|
||||
|
||||
- (void)addDelegate:(id<TUIMessageProgressManagerDelegate>)delegate;
|
||||
- (void)removeDelegate:(id<TUIMessageProgressManagerDelegate>)delegate;
|
||||
|
||||
- (NSInteger)uploadProgressForMessage:(NSString *)msgID;
|
||||
- (NSInteger)downloadProgressForMessage:(NSString *)msgID;
|
||||
- (void)appendUploadProgress:(NSString *)msgID progress:(NSInteger)progress;
|
||||
- (void)appendDownloadProgress:(NSString *)msgID progress:(NSInteger)progress;
|
||||
- (void)notifyMessageSendingResult:(NSString *)msgID result:(TUIMessageSendingResultType)result;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
246
TUIKit/TUIChat/CommonModel/TUIMessageProgressManager.m
Normal file
246
TUIKit/TUIChat/CommonModel/TUIMessageProgressManager.m
Normal file
@@ -0,0 +1,246 @@
|
||||
//
|
||||
// TUIMessageProgressManager.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2022/1/4.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMessageProgressManager.h"
|
||||
#import <ImSDK_Plus/ImSDK_Plus.h>
|
||||
|
||||
@interface TUIMessageProgressManager () <V2TIMSDKListener>
|
||||
|
||||
@property(nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *uploadProgress;
|
||||
|
||||
@property(nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *dowonloadProgress;
|
||||
|
||||
@property(nonatomic, strong) NSHashTable *delegates;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMessageProgressManager
|
||||
|
||||
static id gShareInstance;
|
||||
|
||||
+ (instancetype)shareManager {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
gShareInstance = [[self alloc] init];
|
||||
});
|
||||
return gShareInstance;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
[V2TIMManager.sharedInstance addIMSDKListener:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addDelegate:(id<TUIMessageProgressManagerDelegate>)delegate {
|
||||
if (![NSThread isMainThread]) {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf addDelegate:delegate];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ([self.delegates containsObject:delegate]) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self.delegates addObject:delegate];
|
||||
}
|
||||
|
||||
- (void)removeDelegate:(id<TUIMessageProgressManagerDelegate>)delegate {
|
||||
if (![NSThread isMainThread]) {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf removeDelegate:delegate];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ([self.delegates containsObject:delegate]) {
|
||||
[self.delegates removeObject:delegate];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)getUploadIdentityForMessage:(V2TIMMessage *)message {
|
||||
NSString *msgID = message.msgID;
|
||||
if (message.elemType == V2TIM_ELEM_TYPE_VIDEO) {
|
||||
NSString *path = message.videoElem.videoPath;
|
||||
NSString *uuid = message.videoElem.videoUUID;
|
||||
return [msgID stringByAppendingString:message.videoElem.videoPath];
|
||||
} else if (message.elemType == V2TIM_ELEM_TYPE_IMAGE) {
|
||||
NSString *path = message.imageElem.path;
|
||||
return [msgID stringByAppendingString:message.imageElem.path];
|
||||
} else if (message.elemType == V2TIM_ELEM_TYPE_SOUND) {
|
||||
return [msgID stringByAppendingString:message.soundElem.path];
|
||||
}
|
||||
return msgID;
|
||||
}
|
||||
|
||||
- (NSString *)getDownloadIdentityForMessage:(V2TIMMessage *)message {
|
||||
NSString *msgID = message.msgID;
|
||||
if (message.elemType == V2TIM_ELEM_TYPE_VIDEO) {
|
||||
NSString *uuid = message.videoElem.videoUUID;
|
||||
NSString *snapShotuuid = message.videoElem.snapshotUUID;
|
||||
return [msgID stringByAppendingString:uuid];
|
||||
} else if (message.elemType == V2TIM_ELEM_TYPE_IMAGE) {
|
||||
NSMutableArray<V2TIMImage *> *imageList = message.imageElem.imageList;
|
||||
for (V2TIMImage *img in imageList) {
|
||||
if (img.type == V2TIM_IMAGE_TYPE_ORIGIN) {
|
||||
NSString *url = img.url;
|
||||
}
|
||||
}
|
||||
return @"";
|
||||
}
|
||||
return @"";
|
||||
}
|
||||
|
||||
- (void)uploadCallback:(NSString *)msgID {
|
||||
NSNumber *progress = @(100);
|
||||
if ([self.uploadProgress.allKeys containsObject:msgID]) {
|
||||
progress = [self.uploadProgress objectForKey:msgID];
|
||||
}
|
||||
for (id<TUIMessageProgressManagerDelegate> delegate in self.delegates) {
|
||||
if ([delegate respondsToSelector:@selector(onUploadProgress:progress:)]) {
|
||||
[delegate onUploadProgress:msgID progress:progress.integerValue];
|
||||
}
|
||||
}
|
||||
}
|
||||
- (void)appendUploadProgress:(NSString *)msgID progress:(NSInteger)progress {
|
||||
if (![NSThread isMainThread]) {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf appendUploadProgress:msgID progress:progress];
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (msgID.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([self.uploadProgress.allKeys containsObject:msgID]) {
|
||||
[self.uploadProgress removeObjectForKey:msgID];
|
||||
}
|
||||
if (progress >= 100 || progress <= 0) {
|
||||
[self uploadCallback:msgID];
|
||||
return;
|
||||
}
|
||||
|
||||
[self.uploadProgress setObject:@(progress) forKey:msgID];
|
||||
[self uploadCallback:msgID];
|
||||
}
|
||||
|
||||
- (void)downloadCallback:(NSString *)msgID {
|
||||
NSNumber *progress = @(100);
|
||||
if ([self.dowonloadProgress.allKeys containsObject:msgID]) {
|
||||
progress = [self.dowonloadProgress objectForKey:msgID];
|
||||
}
|
||||
for (id<TUIMessageProgressManagerDelegate> delegate in self.delegates) {
|
||||
if ([delegate respondsToSelector:@selector(onDownloadProgress:progress:)]) {
|
||||
[delegate onDownloadProgress:msgID progress:progress.integerValue];
|
||||
}
|
||||
}
|
||||
}
|
||||
- (void)appendDownloadProgress:(NSString *)msgID progress:(NSInteger)progress {
|
||||
if (![NSThread isMainThread]) {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf appendDownloadProgress:msgID progress:progress];
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (msgID.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([self.dowonloadProgress.allKeys containsObject:msgID]) {
|
||||
[self.dowonloadProgress removeObjectForKey:msgID];
|
||||
}
|
||||
if (progress >= 100 || progress <= 0) {
|
||||
[self downloadCallback:msgID];
|
||||
return;
|
||||
}
|
||||
|
||||
[self.dowonloadProgress setObject:@(progress) forKey:msgID];
|
||||
[self downloadCallback:msgID];
|
||||
}
|
||||
|
||||
- (void)notifyMessageSendingResult:(NSString *)msgID result:(TUIMessageSendingResultType)result {
|
||||
for (id<TUIMessageProgressManagerDelegate> delegate in self.delegates) {
|
||||
if ([delegate respondsToSelector:@selector(onMessageSendingResultChanged:messageID:)]) {
|
||||
[delegate onMessageSendingResultChanged:result messageID:msgID];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)uploadProgressForMessage:(NSString *)msgID {
|
||||
if (![self.uploadProgress.allKeys containsObject:msgID]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
NSInteger progress = 0;
|
||||
@synchronized(self) {
|
||||
progress = [[self.uploadProgress objectForKey:msgID] integerValue];
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
- (NSInteger)downloadProgressForMessage:(NSString *)msgID {
|
||||
if (![self.dowonloadProgress.allKeys containsObject:msgID]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
NSInteger progress = 0;
|
||||
@synchronized(self) {
|
||||
progress = [[self.dowonloadProgress objectForKey:msgID] integerValue];
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
if (![NSThread isMainThread]) {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf reset];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
[self.uploadProgress removeAllObjects];
|
||||
[self.dowonloadProgress removeAllObjects];
|
||||
}
|
||||
|
||||
- (NSMutableDictionary<NSString *, NSNumber *> *)uploadProgress {
|
||||
if (_uploadProgress == nil) {
|
||||
_uploadProgress = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return _uploadProgress;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary<NSString *, NSNumber *> *)dowonloadProgress {
|
||||
if (_dowonloadProgress == nil) {
|
||||
_dowonloadProgress = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return _dowonloadProgress;
|
||||
}
|
||||
|
||||
- (NSHashTable *)delegates {
|
||||
if (_delegates == nil) {
|
||||
_delegates = [NSHashTable weakObjectsHashTable];
|
||||
}
|
||||
return _delegates;
|
||||
}
|
||||
|
||||
- (void)onConnecting {
|
||||
[self reset];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// UIAlertController+TUICustomStyle.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/10/20.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUICustomActionSheetItem : NSObject
|
||||
|
||||
@property(nonatomic, assign) NSInteger priority;
|
||||
|
||||
@property(nonatomic, copy) NSString *title;
|
||||
|
||||
@property(nonatomic, strong) UIImage *leftMark;
|
||||
|
||||
@property(nonatomic, assign) UIAlertActionStyle actionStyle;
|
||||
|
||||
@property(nonatomic, copy) void (^actionHandler)(UIAlertAction *action);
|
||||
|
||||
- (instancetype)initWithTitle:(NSString *)title leftMark:(UIImage *)leftMark withActionHandler:(void (^)(UIAlertAction *action))actionHandler;
|
||||
@end
|
||||
|
||||
@interface UIAlertController (TUICustomStyle)
|
||||
|
||||
- (void)configItems:(NSArray<TUICustomActionSheetItem *> *)items;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// UIAlertController+TUICustomStyle.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/10/20.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <objc/runtime.h>
|
||||
#import "UIAlertController+TUICustomStyle.h"
|
||||
|
||||
@implementation TUICustomActionSheetItem
|
||||
|
||||
- (instancetype)initWithTitle:(NSString *)title leftMark:(UIImage *)leftMark withActionHandler:(void (^)(UIAlertAction *action))actionHandler {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_title = title;
|
||||
_leftMark = leftMark;
|
||||
_actionHandler = actionHandler;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
|
||||
CGFloat padding = 10;
|
||||
CGFloat itemHeight = 57;
|
||||
CGFloat lineHeight = 0.5;
|
||||
CGFloat itemCount = 2;
|
||||
|
||||
@interface UIAlertController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIAlertController (TUICustomStyle)
|
||||
|
||||
- (void)configItems:(NSArray<TUICustomActionSheetItem *> *)items {
|
||||
CGFloat alertVCWidth = self.view.frame.size.width - 2 * padding;
|
||||
|
||||
if (items.count > 0) {
|
||||
for (int i = 0; i < items.count; i++) {
|
||||
UIView *itemView = [[UIView alloc] init];
|
||||
itemView.frame = CGRectMake(padding, (itemHeight + lineHeight) * i, alertVCWidth - 2 * padding, itemHeight);
|
||||
itemView.userInteractionEnabled = NO;
|
||||
[self.view addSubview:itemView];
|
||||
|
||||
UIImageView *icon = [[UIImageView alloc] init];
|
||||
[itemView addSubview:icon];
|
||||
icon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
icon.frame = CGRectMake(kScale390(20), itemHeight * 0.5 - kScale390(30) * 0.5, kScale390(30), kScale390(30));
|
||||
icon.image = items[i].leftMark;
|
||||
|
||||
UILabel *l = [[UILabel alloc] init];
|
||||
l.frame = CGRectMake(icon.frame.origin.x + icon.frame.size.width + padding + kScale390(15), 0, alertVCWidth * 0.5, itemHeight);
|
||||
l.text = items[i].title;
|
||||
l.font = [UIFont systemFontOfSize:17];
|
||||
l.textAlignment = isRTL()? NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
l.textColor = [UIColor systemBlueColor];
|
||||
l.userInteractionEnabled = false;
|
||||
[itemView addSubview:l];
|
||||
|
||||
if (isRTL()) {
|
||||
[icon resetFrameToFitRTL];
|
||||
[l resetFrameToFitRTL];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// actions
|
||||
if (items.count > 0) {
|
||||
for (int i = 0; i < items.count; i++) {
|
||||
UIAlertAction *action = [UIAlertAction actionWithTitle:@"" style:items[i].actionStyle handler:items[i].actionHandler];
|
||||
[self addAction:action];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user