增加换肤功能

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

View File

@@ -0,0 +1,193 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
/**
* This file declares the TUIInputBarDelegate protocol and the TUIInputBar class.
* TUI input bar, a UI component used to detect and obtain user input.
* TUIInputBar, the UI component at the bottom of the chat message. Includes text input box, emoji button, voice button, and "+" button ("More" button)
* TUIInputBarDelegate provides callbacks for various situations of the input bar, including the callback for the emoticon of clicking the input bar, the
* "more" view, and the voice button. And callbacks to send message, send voice, change input height.
*/
#import <TIMCommon/TIMCommonModel.h>
#import <TUICore/TUIThemeManager.h>
#import <UIKit/UIKit.h>
#import "TUIResponderTextView_Minimalist.h"
#define kTUIInputNoramlFont [UIFont systemFontOfSize:16.0]
#define kTUIInputNormalTextColor TUIChatDynamicColor(@"chat_input_text_color", @"#000000")
typedef NS_ENUM(NSInteger, TUIRecordStatus) {
TUIRecordStatus_Init,
TUIRecordStatus_Record,
TUIRecordStatus_Delete,
TUIRecordStatus_Cancel,
};
@class TUIInputBar_Minimalist;
/////////////////////////////////////////////////////////////////////////////////
//
// TUIInputBarDelegate
//
/////////////////////////////////////////////////////////////////////////////////
@protocol TUIInputBarDelegate_Minimalist <NSObject>
/**
* Callback after clicking the emoji button - "smiley" button.
* You can use this callback to achieve: After clicking the emoticon button, the corresponding emoticon view is displayed.
*/
- (void)inputBarDidTouchFace:(TUIInputBar_Minimalist *)textView;
/**
* Callback after more button - "+" is clicked.
* You can use this callback to achieve: corresponding user's click operation to display more corresponding views.
*/
- (void)inputBarDidTouchMore:(TUIInputBar_Minimalist *)textView;
/**
* You can use this callback to achieve: jump to the camera interface, take a photo or select a local photo
*/
- (void)inputBarDidTouchCamera:(TUIInputBar_Minimalist *)textView;
/**
* Callback when input bar height changes
* This callback is fired when the InputBar height changes when you click the voice button, emoji button, "+" button, or call out/retract the keyboard
* You can use this callback to achieve: UI layout adjustment when InputBar height changes through this callback function.
* In the default implementation of TUIKit, this callback function further calls the didChangeHeight delegate in TUIInputController to adjust the height of the
* UI layout after processing the appearance of the expression view and more views.
*/
- (void)inputBar:(TUIInputBar_Minimalist *)textView didChangeInputHeight:(CGFloat)offset;
/**
* Callback when sending a text message.
* This callback is fired when you send a text message through the InputBar (click the send button from the keyboard).
* You can use this callback to get the content of the InputBar and send the message.
* In the default implementation of TUIKit, this callback further calls the didSendMessage delegate in TUIInputController for further logical processing of
* message sending after processing the appearance of the expression view and more views.
*/
- (void)inputBar:(TUIInputBar_Minimalist *)textView didSendText:(NSString *)text;
/**
* Callback when sending voice
* This callback is triggered when you long press and release the voice button.
* You can use this callback to process the recorded voice information and send the voice message.
* In the default implementation of TUIKit, this callback function further calls the didSendMessage delegate in TUIInputController for further logical
* processing of message sending after processing the appearance of the expression view and more views.
*/
- (void)inputBar:(TUIInputBar_Minimalist *)textView didSendVoice:(NSString *)path;
/**
* Callback after entering text containing the @ character
*/
- (void)inputBarDidInputAt:(TUIInputBar_Minimalist *)textView;
/**
* Callback after removing text containing @ characters (e.g. removing @xxx)
*/
- (void)inputBar:(TUIInputBar_Minimalist *)textView didDeleteAt:(NSString *)text;
/**
* Callback after keyboard button click
* After clicking the emoticon button, the "smiley face" icon at the corresponding position will become the "keyboard" icon, which is the keyboard button at
* this time. You can use this callback to: hide the currently displayed emoticon view or more views, and open the keyboard.
*/
- (void)inputBarDidTouchKeyboard:(TUIInputBar_Minimalist *)textView;
/**
* Callback after clicking delete button on keyboard
*/
- (void)inputBarDidDeleteBackward:(TUIInputBar_Minimalist *)textView;
- (void)inputTextViewShouldBeginTyping:(UITextView *)textView;
- (void)inputTextViewShouldEndTyping:(UITextView *)textView;
@end
/////////////////////////////////////////////////////////////////////////////////
//
// TUIInputBar
//
/////////////////////////////////////////////////////////////////////////////////
@interface TUIInputBar_Minimalist : UIView
/**
* Separtor
*/
@property(nonatomic, strong) UIView *lineView;
/**
* Voice button
* Switch to voice input state after clicking
*/
@property(nonatomic, strong) UIButton *micButton;
/**
* Camera button
* Switch to camera after clicking
*/
@property(nonatomic, strong) UIButton *cameraButton;
/**
* Keyboard button
* Switch to keyboard input state after clicking
*/
@property(nonatomic, strong) UIButton *keyboardButton;
/**
* Input view
*/
@property(nonatomic, strong) TUIResponderTextView_Minimalist *inputTextView;
/**
* Emoticon button
* Switch to emoji input state after clicking
*/
@property(nonatomic, strong) UIButton *faceButton;
/**
* More button
* A button that, when clicked, opens up more menu options
*/
@property(nonatomic, strong) UIButton *moreButton;
/**
* Record View
*/
@property(nonatomic, strong) UIView *recordView;
@property(nonatomic, strong) UIImageView *recordDeleteView;
@property(nonatomic, strong) UIView *recordBackgroudView;
@property(nonatomic, strong) UIView *recordTipsView;
@property(nonatomic, strong) UILabel *recordTipsLabel;
@property(nonatomic, strong) UILabel *recordTimeLabel;
@property(nonatomic, strong) NSMutableArray *recordAnimateViews;
@property(nonatomic, strong) UIImageView *recordAnimateCoverView;
@property(nonatomic, assign) CGRect recordAnimateCoverViewFrame;
@property(nonatomic, weak) id<TUIInputBarDelegate_Minimalist> delegate;
@property(nonatomic, copy) void (^inputBarTextChanged)(UITextView * textview);
/**
*
* Add emoticon text
* Used to input emoticon text in the current text input box
*
* @param emoji The string representation of the emoticon to be entered.
*/
- (void)addEmoji:(TUIFaceCellData *)emoji;
- (void)backDelete;
- (void)clearInput;
- (NSString *)getInput;
- (void)updateTextViewFrame;
- (void)changeToKeyboard;
- (void)addDraftToInputBar:(NSAttributedString *)draft;
- (void)addWordsToInputBar:(NSAttributedString *)words;
@end

View File

@@ -0,0 +1,667 @@
//
// TUIInputBar_Minimalist.m
// UIKit
//
// Created by kennethmiao on 2018/9/18.
// Copyright © 2018 Tencent. All rights reserved.
//
#import "TUIInputBar_Minimalist.h"
#import <TIMCommon/NSString+TUIEmoji.h>
#import <TIMCommon/NSTimer+TUISafe.h>
#import <TIMCommon/TIMDefine.h>
#import <TUICore/TUICore.h>
#import <TUICore/TUIDarkModel.h>
#import <TUICore/TUIGlobalization.h>
#import <TUICore/TUITool.h>
#import <TUICore/UIView+TUILayout.h>
#import "ReactiveObjC/ReactiveObjC.h"
#import "TUIAudioRecorder.h"
#import "TUIChatConfig.h"
@interface TUIInputBar_Minimalist () <UITextViewDelegate, TUIAudioRecorderDelegate>
@property(nonatomic, strong) NSDate *recordStartTime;
@property(nonatomic, strong) TUIAudioRecorder *recorder;
@property(nonatomic, strong) NSTimer *recordTimer;
@property(nonatomic, assign) BOOL isFocusOn;
@property(nonatomic, strong) NSTimer *sendTypingStatusTimer;
@property(nonatomic, assign) BOOL allowSendTypingStatusByChangeWord;
@end
@implementation TUIInputBar_Minimalist
- (void)dealloc {
if (_sendTypingStatusTimer) {
[_sendTypingStatusTimer invalidate];
_sendTypingStatusTimer = nil;
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupViews];
[self defaultLayout];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onThemeChanged) name:TUIDidApplyingThemeChangedNotfication object:nil];
}
return self;
}
- (void)setupViews {
self.backgroundColor = RGBA(255, 255, 255, 1);
_lineView = [[UIView alloc] init];
_lineView.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#FFFFFF");
_moreButton = [[UIButton alloc] init];
[_moreButton addTarget:self action:@selector(clickMoreBtn:) forControlEvents:UIControlEventTouchUpInside];
[_moreButton setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"TypeSelectorBtnHL_Black")]
forState:UIControlStateNormal];
[_moreButton setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"TypeSelectorBtnHL_Black")]
forState:UIControlStateHighlighted];
[self addSubview:_moreButton];
_inputTextView = [[TUIResponderTextView_Minimalist alloc] init];
_inputTextView.delegate = self;
[_inputTextView setFont:kTUIInputNoramlFont];
_inputTextView.backgroundColor = TUIChatDynamicColor(@"chat_input_bg_color", @"#FFFFFF");
_inputTextView.textColor = TUIChatDynamicColor(@"chat_input_text_color", @"#000000");
UIEdgeInsets ei = UIEdgeInsetsMake(kScale390(9), kScale390(16), kScale390(9), kScale390(30));
_inputTextView.textContainerInset = rtlEdgeInsetsWithInsets(ei);
_inputTextView.textAlignment = isRTL()?NSTextAlignmentRight: NSTextAlignmentLeft;
[_inputTextView setReturnKeyType:UIReturnKeySend];
[self addSubview:_inputTextView];
_keyboardButton = [[UIButton alloc] init];
[_keyboardButton addTarget:self action:@selector(clickKeyboardBtn:) forControlEvents:UIControlEventTouchUpInside];
[_keyboardButton setImage:TUIChatBundleThemeImage(@"chat_ToolViewKeyboard_img", @"ToolViewKeyboard") forState:UIControlStateNormal];
[_keyboardButton setImage:TUIChatBundleThemeImage(@"chat_ToolViewKeyboardHL_img", @"ToolViewKeyboardHL") forState:UIControlStateHighlighted];
_keyboardButton.hidden = YES;
[self addSubview:_keyboardButton];
_faceButton = [[UIButton alloc] init];
[_faceButton addTarget:self action:@selector(clickFaceBtn:) forControlEvents:UIControlEventTouchUpInside];
[_faceButton setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"ToolViewEmotion")] forState:UIControlStateNormal];
[_faceButton setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"ToolViewEmotion")]
forState:UIControlStateHighlighted];
[self addSubview:_faceButton];
_micButton = [[UIButton alloc] init];
[_micButton addTarget:self action:@selector(recordBtnDown:) forControlEvents:UIControlEventTouchDown];
[_micButton addTarget:self action:@selector(recordBtnUp:) forControlEvents:UIControlEventTouchUpInside];
[_micButton addTarget:self action:@selector(recordBtnCancel:) forControlEvents:UIControlEventTouchUpOutside | UIControlEventTouchCancel];
[_micButton addTarget:self action:@selector(recordBtnDragExit:) forControlEvents:UIControlEventTouchDragExit];
[_micButton addTarget:self action:@selector(recordBtnDragEnter:) forControlEvents:UIControlEventTouchDragEnter];
[_micButton setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"ToolViewInputVoice")]
forState:UIControlStateNormal];
[self addSubview:_micButton];
_cameraButton = [[UIButton alloc] init];
[_cameraButton addTarget:self action:@selector(clickCameraBtn:) forControlEvents:UIControlEventTouchUpInside];
[_cameraButton setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"ToolViewInputCamera")]
forState:UIControlStateNormal];
[_cameraButton setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"ToolViewInputCamera")]
forState:UIControlStateHighlighted];
[self addSubview:_cameraButton];
[self initRecordView];
}
- (void)initRecordView {
_recordView = [[UIView alloc] init];
_recordView.backgroundColor = RGBA(255, 255, 255, 1);
_recordView.hidden = YES;
[self addSubview:_recordView];
_recordDeleteView = [[UIImageView alloc] init];
[_recordView addSubview:_recordDeleteView];
_recordBackgroudView = [[UIView alloc] init];
[_recordView addSubview:_recordBackgroudView];
_recordTimeLabel = [[UILabel alloc] init];
_recordTimeLabel.textColor = [UIColor whiteColor];
_recordTimeLabel.font = [UIFont systemFontOfSize:14];
[_recordView addSubview:_recordTimeLabel];
_recordAnimateViews = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
UIImageView *recordAnimateView = [[UIImageView alloc] init];
[recordAnimateView setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"voice_record_animation")]];
[_recordView addSubview:recordAnimateView];
[_recordAnimateViews addObject:recordAnimateView];
}
_recordAnimateCoverView = [[UIImageView alloc] init];
[_recordView addSubview:_recordAnimateCoverView];
_recordTipsView = [[UIView alloc] init];
_recordTipsView.backgroundColor = [UIColor whiteColor];
_recordTipsView.frame = CGRectMake(0, -56, Screen_Width, 56);
[_recordView addSubview:_recordTipsView];
_recordTipsLabel = [[UILabel alloc] init];
_recordTipsLabel.textColor = RGBA(102, 102, 102, 1);
_recordTipsLabel.textColor = [UIColor blackColor];
_recordTipsLabel.text = TIMCommonLocalizableString(TUIKitInputRecordTipsTitle);
_recordTipsLabel.textAlignment = NSTextAlignmentCenter;
_recordTipsLabel.font = [UIFont systemFontOfSize:14];
_recordTipsLabel.frame = CGRectMake(0, 10, Screen_Width, 22);
[_recordTipsView addSubview:_recordTipsLabel];
[self setRecordStatus:TUIRecordStatus_Init];
}
- (void)setRecordStatus:(TUIRecordStatus)status {
switch (status) {
case TUIRecordStatus_Init:
case TUIRecordStatus_Record:
case TUIRecordStatus_Cancel: {
_recordDeleteView.frame = CGRectMake(kScale390(16), _recordDeleteView.mm_y, 24, 24);
if (isRTL()){
[_recordDeleteView resetFrameToFitRTL];
}
_recordDeleteView.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"voice_record_delete")];
_recordBackgroudView.backgroundColor = RGBA(20, 122, 255, 1);
_recordAnimateCoverView.backgroundColor = _recordBackgroudView.backgroundColor;
_recordAnimateCoverView.frame = self.recordAnimateCoverViewFrame;
_recordTimeLabel.text = @"0:00";
_recordTipsLabel.text = TIMCommonLocalizableString(TUIKitInputRecordTipsTitle);
if (TUIRecordStatus_Record == status) {
_recordView.hidden = NO;
} else {
_recordView.hidden = YES;
}
} break;
case TUIRecordStatus_Delete: {
_recordDeleteView.frame = CGRectMake(0, _recordDeleteView.mm_y, 20, 24);
if (isRTL()){
[_recordDeleteView resetFrameToFitRTL];
}
_recordDeleteView.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"voice_record_delete_ready")];
_recordBackgroudView.backgroundColor = RGBA(255, 88, 76, 1);
_recordAnimateCoverView.backgroundColor = _recordBackgroudView.backgroundColor;
_recordTipsLabel.text = TIMCommonLocalizableString(TUIKitInputRecordCancelTipsTitle);
_recordView.hidden = NO;
} break;
default:
break;
}
}
- (void)applyBorderTheme {
if (_recordBackgroudView) {
[_recordBackgroudView.layer setMasksToBounds:YES];
[_recordBackgroudView.layer setCornerRadius:_recordBackgroudView.mm_h / 2.0];
}
if (_inputTextView) {
[_inputTextView.layer setMasksToBounds:YES];
[_inputTextView.layer setCornerRadius:_inputTextView.mm_h / 2.0];
[_inputTextView.layer setBorderWidth:0.5f];
[_inputTextView.layer setBorderColor:RGBA(221, 221, 221, 1).CGColor];
}
}
- (void)defaultLayout {
_lineView.frame = CGRectMake(0, 0, Screen_Width, TLine_Heigh);
CGFloat iconSize = 24;
_moreButton.frame = CGRectMake(kScale390(16), kScale390(13), iconSize, iconSize);
_cameraButton.frame = CGRectMake(Screen_Width - kScale390(16) - iconSize, 13, iconSize, iconSize);
_micButton.frame = CGRectMake(Screen_Width - kScale390(56) - iconSize, 13, iconSize, iconSize);
CGFloat faceSize = 19;
_faceButton.frame = CGRectMake(_micButton.mm_x - kScale390(50), 15, faceSize, faceSize);
_keyboardButton.frame = _faceButton.frame;
_inputTextView.frame = CGRectMake(kScale390(56), 7, Screen_Width - kScale390(152), 36);
_recordView.frame = CGRectMake(0, _inputTextView.mm_y, self.mm_w, _inputTextView.mm_h);
_recordDeleteView.frame = CGRectMake(kScale390(16), 4, iconSize, iconSize);
_recordBackgroudView.frame = CGRectMake(kScale390(54), 0, self.mm_w - kScale390(70), _recordView.mm_h);
_recordTimeLabel.frame = CGRectMake(kScale390(70), kScale390(7), 32, 22);
CGFloat animationStartX = kScale390(112);
CGFloat animationY = 8;
CGFloat animationSize = 20;
CGFloat animationSpace = kScale390(8);
CGFloat animationCoverWidth = 0;
for (int i = 0; i < self.recordAnimateViews.count; ++i) {
UIView *animationView = self.recordAnimateViews[i];
animationView.frame = CGRectMake(animationStartX + (animationSize + animationSpace) * i, animationY, animationSize, animationSize);
animationCoverWidth = (animationSize + animationSpace) * (i + 1);
}
_recordAnimateCoverViewFrame = CGRectMake(animationStartX, animationY, animationCoverWidth, animationSize);
_recordAnimateCoverView.frame = self.recordAnimateCoverViewFrame;
[self applyBorderTheme];
if(isRTL()) {
for (UIView *subviews in self.subviews) {
[subviews resetFrameToFitRTL];
}
for (UIView *subview in _recordView.subviews) {
[subview resetFrameToFitRTL];
}
}
}
- (void)layoutButton:(CGFloat)height {
CGRect frame = self.frame;
CGFloat offset = height - frame.size.height;
frame.size.height = height;
self.frame = frame;
if (_delegate && [_delegate respondsToSelector:@selector(inputBar:didChangeInputHeight:)]) {
[_delegate inputBar:self didChangeInputHeight:offset];
}
}
- (void)clickCameraBtn:(UIButton *)sender {
_micButton.hidden = NO;
_keyboardButton.hidden = YES;
_inputTextView.hidden = NO;
_faceButton.hidden = NO;
[self setRecordStatus:TUIRecordStatus_Cancel];
if (_delegate && [_delegate respondsToSelector:@selector(inputBarDidTouchCamera:)]) {
[_delegate inputBarDidTouchCamera:self];
}
}
- (void)clickKeyboardBtn:(UIButton *)sender {
_micButton.hidden = NO;
_keyboardButton.hidden = YES;
_inputTextView.hidden = NO;
_faceButton.hidden = NO;
[self setRecordStatus:TUIRecordStatus_Cancel];
[self layoutButton:_inputTextView.frame.size.height + 2 * TTextView_Margin];
if (_delegate && [_delegate respondsToSelector:@selector(inputBarDidTouchKeyboard:)]) {
[_delegate inputBarDidTouchKeyboard:self];
}
}
- (void)clickFaceBtn:(UIButton *)sender {
_micButton.hidden = NO;
_faceButton.hidden = YES;
_keyboardButton.hidden = NO;
_inputTextView.hidden = NO;
[self setRecordStatus:TUIRecordStatus_Cancel];
if (_delegate && [_delegate respondsToSelector:@selector(inputBarDidTouchFace:)]) {
[_delegate inputBarDidTouchFace:self];
}
_keyboardButton.frame = _faceButton.frame;
}
- (void)clickMoreBtn:(UIButton *)sender {
if (_delegate && [_delegate respondsToSelector:@selector(inputBarDidTouchMore:)]) {
[_delegate inputBarDidTouchMore:self];
}
}
- (void)recordBtnDown:(UIButton *)sender {
[self.recorder record];
}
- (void)recordBtnUp:(UIButton *)sender {
NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:_recordStartTime];
if (interval < 1) {
[self.recorder cancel];
} else if (interval > 60) {
[self.recorder cancel];
} else {
[self.recorder stop];
NSString *path = self.recorder.recordedFilePath;
if (path) {
if (_delegate && [_delegate respondsToSelector:@selector(inputBar:didSendVoice:)]) {
[_delegate inputBar:self didSendVoice:path];
}
}
}
[self setRecordStatus:TUIRecordStatus_Cancel];
}
- (void)recordBtnCancel:(UIGestureRecognizer *)gesture {
[self setRecordStatus:TUIRecordStatus_Cancel];
[self.recorder cancel];
}
- (void)recordBtnDragExit:(UIButton *)sender {
[self setRecordStatus:TUIRecordStatus_Delete];
}
- (void)recordBtnDragEnter:(UIButton *)sender {
[self setRecordStatus:TUIRecordStatus_Record];
}
- (void)showHapticFeedback {
if (@available(iOS 10.0, *)) {
dispatch_async(dispatch_get_main_queue(), ^{
UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
[generator prepare];
[generator impactOccurred];
});
} else {
// Fallback on earlier versions
}
}
#pragma mark - talk
- (void)textViewDidBeginEditing:(UITextView *)textView {
self.keyboardButton.hidden = YES;
self.micButton.hidden = NO;
self.faceButton.hidden = NO;
self.isFocusOn = YES;
self.allowSendTypingStatusByChangeWord = YES;
__weak typeof(self) weakSelf = self;
self.sendTypingStatusTimer = [NSTimer tui_scheduledTimerWithTimeInterval:4
repeats:YES
block:^(NSTimer *_Nonnull timer) {
__strong typeof(weakSelf) strongSelf = weakSelf;
strongSelf.allowSendTypingStatusByChangeWord = YES;
}];
if (self.isFocusOn && [textView.textStorage tui_getPlainString].length > 0) {
if (_delegate && [_delegate respondsToSelector:@selector(inputTextViewShouldBeginTyping:)]) {
[_delegate inputTextViewShouldBeginTyping:textView];
}
}
}
- (void)textViewDidEndEditing:(UITextView *)textView {
self.isFocusOn = NO;
if (_delegate && [_delegate respondsToSelector:@selector(inputTextViewShouldEndTyping:)]) {
[_delegate inputTextViewShouldEndTyping:textView];
}
}
- (void)textViewDidChange:(UITextView *)textView {
if (self.allowSendTypingStatusByChangeWord && self.isFocusOn && [textView.textStorage tui_getPlainString].length > 0) {
if (_delegate && [_delegate respondsToSelector:@selector(inputTextViewShouldBeginTyping:)]) {
self.allowSendTypingStatusByChangeWord = NO;
[_delegate inputTextViewShouldBeginTyping:textView];
}
}
if (self.isFocusOn && [textView.textStorage tui_getPlainString].length == 0) {
if (_delegate && [_delegate respondsToSelector:@selector(inputTextViewShouldEndTyping:)]) {
[_delegate inputTextViewShouldEndTyping:textView];
}
}
if (self.inputBarTextChanged) {
self.inputBarTextChanged(_inputTextView);
}
CGSize size = [_inputTextView sizeThatFits:CGSizeMake(_inputTextView.frame.size.width, TTextView_TextView_Height_Max)];
CGFloat oldHeight = _inputTextView.frame.size.height;
CGFloat newHeight = size.height;
if (newHeight > TTextView_TextView_Height_Max) {
newHeight = TTextView_TextView_Height_Max;
}
if (newHeight < TTextView_TextView_Height_Min) {
newHeight = TTextView_TextView_Height_Min;
}
if (oldHeight == newHeight) {
return;
}
__weak typeof(self) ws = self;
[UIView animateWithDuration:0.3
animations:^{
CGRect textFrame = ws.inputTextView.frame;
textFrame.size.height += newHeight - oldHeight;
ws.inputTextView.frame = textFrame;
[ws layoutButton:newHeight + 2 * TTextView_Margin];
}];
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ([text tui_containsString:@"["] && [text tui_containsString:@"]"]) {
NSRange selectedRange = textView.selectedRange;
if (selectedRange.length > 0) {
[textView.textStorage deleteCharactersInRange:selectedRange];
}
NSMutableAttributedString *textChange = [text getAdvancedFormatEmojiStringWithFont:kTUIInputNoramlFont
textColor:kTUIInputNormalTextColor
emojiLocations:nil];
[textView.textStorage insertAttributedString:textChange atIndex:textView.textStorage.length];
dispatch_async(dispatch_get_main_queue(), ^{
self.inputTextView.selectedRange = NSMakeRange(self.inputTextView.textStorage.length + 1, 0);
});
return NO;
}
if ([text isEqualToString:@"\n"]) {
if (_delegate && [_delegate respondsToSelector:@selector(inputBar:didSendText:)]) {
NSString *sp = [[textView.textStorage tui_getPlainString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if (sp.length == 0) {
UIAlertController *ac = [UIAlertController alertControllerWithTitle:TIMCommonLocalizableString(TUIKitInputBlankMessageTitle)
message:nil
preferredStyle:UIAlertControllerStyleAlert];
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(Confirm) style:UIAlertActionStyleDefault handler:nil]];
[self.mm_viewController presentViewController:ac animated:YES completion:nil];
} else {
[_delegate inputBar:self didSendText:[textView.textStorage tui_getPlainString]];
[self clearInput];
}
}
return NO;
} else if ([text isEqualToString:@""]) {
if (textView.textStorage.length > range.location) {
// Delete the @ message like @xxx at one time
NSAttributedString *lastAttributedStr = [textView.textStorage attributedSubstringFromRange:NSMakeRange(range.location, 1)];
NSString *lastStr = [lastAttributedStr tui_getPlainString];
if (lastStr && lastStr.length > 0 && [lastStr characterAtIndex:0] == ' ') {
NSUInteger location = range.location;
NSUInteger length = range.length;
// corresponds to ascii code
int at = 64;
// (space) ascii
// Space (space) corresponding ascii code
int space = 32;
while (location != 0) {
location--;
length++;
// Convert characters to ascii code, copy to int, avoid out of bounds
int c = (int)[[[textView.textStorage attributedSubstringFromRange:NSMakeRange(location, 1)] tui_getPlainString] characterAtIndex:0];
if (c == at) {
NSString *atText = [[textView.textStorage attributedSubstringFromRange:NSMakeRange(location, length)] tui_getPlainString];
UIFont *textFont = kTUIInputNoramlFont;
NSAttributedString *spaceString = [[NSAttributedString alloc] initWithString:@"" attributes:@{NSFontAttributeName : textFont}];
[textView.textStorage replaceCharactersInRange:NSMakeRange(location, length) withAttributedString:spaceString];
if (self.delegate && [self.delegate respondsToSelector:@selector(inputBar:didDeleteAt:)]) {
[self.delegate inputBar:self didDeleteAt:atText];
}
return NO;
} else if (c == space) {
// Avoid "@nickname Hello, nice to meet you (space) "" Press del after a space to over-delete to @
break;
}
}
}
}
}
// Monitor the input of @ character, including full-width/half-width
else if ([text isEqualToString:@"@"] || [text isEqualToString:@""]) {
if (self.delegate && [self.delegate respondsToSelector:@selector(inputBarDidInputAt:)]) {
[self.delegate inputBarDidInputAt:self];
}
return NO;
}
return YES;
}
- (void)onDeleteBackward:(TUIResponderTextView_Minimalist *)textView {
if (self.delegate && [self.delegate respondsToSelector:@selector(inputBarDidDeleteBackward:)]) {
[self.delegate inputBarDidDeleteBackward:self];
}
}
- (void)clearInput {
[_inputTextView.textStorage deleteCharactersInRange:NSMakeRange(0, _inputTextView.textStorage.length)];
[self textViewDidChange:_inputTextView];
}
- (NSString *)getInput {
return [_inputTextView.textStorage tui_getPlainString];
}
- (void)addEmoji:(TUIFaceCellData *)emoji {
// Create emoji attachment
TUIEmojiTextAttachment *emojiTextAttachment = [[TUIEmojiTextAttachment alloc] init];
emojiTextAttachment.faceCellData = emoji;
// Set tag and image
emojiTextAttachment.emojiTag = emoji.name;
emojiTextAttachment.image = [[TUIImageCache sharedInstance] getFaceFromCache:emoji.path];
// Set emoji size
emojiTextAttachment.emojiSize = kTIMDefaultEmojiSize;
NSAttributedString *str = [NSAttributedString attributedStringWithAttachment:emojiTextAttachment];
NSRange selectedRange = _inputTextView.selectedRange;
if (selectedRange.length > 0) {
[_inputTextView.textStorage deleteCharactersInRange:selectedRange];
}
// Insert emoji image
[_inputTextView.textStorage insertAttributedString:str atIndex:_inputTextView.selectedRange.location];
_inputTextView.selectedRange = NSMakeRange(_inputTextView.selectedRange.location + 1, 0);
[self resetTextStyle];
if (_inputTextView.contentSize.height > TTextView_TextView_Height_Max) {
float offset = _inputTextView.contentSize.height - _inputTextView.frame.size.height;
[_inputTextView scrollRectToVisible:CGRectMake(0, offset, _inputTextView.frame.size.width, _inputTextView.frame.size.height) animated:YES];
}
[self textViewDidChange:_inputTextView];
}
- (void)resetTextStyle {
// After changing text selection, should reset style.
NSRange wholeRange = NSMakeRange(0, _inputTextView.textStorage.length);
[_inputTextView.textStorage removeAttribute:NSFontAttributeName range:wholeRange];
[_inputTextView.textStorage removeAttribute:NSForegroundColorAttributeName range:wholeRange];
[_inputTextView.textStorage addAttribute:NSForegroundColorAttributeName value:kTUIInputNormalTextColor range:wholeRange];
[_inputTextView.textStorage addAttribute:NSFontAttributeName value:kTUIInputNoramlFont range:wholeRange];
[_inputTextView setFont:kTUIInputNoramlFont];
_inputTextView.textAlignment = isRTL()?NSTextAlignmentRight: NSTextAlignmentLeft;
}
- (void)backDelete {
if (_inputTextView.textStorage.length > 0) {
[_inputTextView.textStorage deleteCharactersInRange:NSMakeRange(_inputTextView.textStorage.length - 1, 1)];
[self textViewDidChange:_inputTextView];
}
}
- (void)updateTextViewFrame {
[self textViewDidChange:[UITextView new]];
}
- (void)changeToKeyboard {
[self clickKeyboardBtn:self.keyboardButton];
}
- (void)onThemeChanged {
[self applyBorderTheme];
}
- (void)addDraftToInputBar:(NSAttributedString *)draft {
[self addWordsToInputBar:draft];
}
- (void)addWordsToInputBar:(NSAttributedString *)words {
NSRange selectedRange = self.inputTextView.selectedRange;
if (selectedRange.length > 0) {
[self.inputTextView.textStorage deleteCharactersInRange:selectedRange];
}
// Insert words
[self.inputTextView.textStorage insertAttributedString:words atIndex:self.inputTextView.selectedRange.location];
self.inputTextView.selectedRange = NSMakeRange(self.inputTextView.textStorage.length + 1, 0);
[self resetTextStyle];
[self updateTextViewFrame];
}
#pragma mark - TUIAudioRecorderDelegate
- (void)audioRecorder:(TUIAudioRecorder *)recorder didCheckPermission:(BOOL)isGranted isFirstTime:(BOOL)isFirstTime {
if (isFirstTime) {
if (!isGranted) {
[self showRequestMicAuthorizationAlert];
}
return;
}
[self setRecordStatus:TUIRecordStatus_Record];
_recordStartTime = [NSDate date];
[self showHapticFeedback];
}
- (void)showRequestMicAuthorizationAlert {
UIAlertController *ac = [UIAlertController alertControllerWithTitle:TIMCommonLocalizableString(TUIKitInputNoMicTitle)
message:TIMCommonLocalizableString(TUIKitInputNoMicTips)
preferredStyle:UIAlertControllerStyleAlert];
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(TUIKitInputNoMicOperateLater) style:UIAlertActionStyleCancel handler:nil]];
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(TUIKitInputNoMicOperateEnable)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
UIApplication *app = [UIApplication sharedApplication];
NSURL *settingsURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
if ([app canOpenURL:settingsURL]) {
[app openURL:settingsURL];
}
}]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.mm_viewController presentViewController:ac animated:YES completion:nil];
});
}
- (void)audioRecorder:(TUIAudioRecorder *)recorder didRecordTimeChanged:(NSTimeInterval)time {
float maxDuration = MIN(59.7, [TUIChatConfig defaultConfig].maxAudioRecordDuration);
NSInteger seconds = maxDuration - time;
_recordTimeLabel.text = [NSString stringWithFormat:@"%d:%.2d", (int)time / 60, (int)time % 60 + 1];
CGFloat width = _recordAnimateCoverViewFrame.size.width;
int interval_ms = (int)(time * 1000);
int runloop_ms = 5 * 1000;
CGFloat offset_x = width * (interval_ms % runloop_ms) / runloop_ms;
_recordAnimateCoverView.frame = CGRectMake(_recordAnimateCoverViewFrame.origin.x + offset_x, _recordAnimateCoverViewFrame.origin.y, width - offset_x,
_recordAnimateCoverViewFrame.size.height);
if (time > maxDuration) {
[self.recorder stop];
[self setRecordStatus:TUIRecordStatus_Cancel];
NSString *path = self.recorder.recordedFilePath;
if (path) {
if (_delegate && [_delegate respondsToSelector:@selector(inputBar:didSendVoice:)]) {
[_delegate inputBar:self didSendVoice:path];
}
}
}
}
#pragma mark - Getter
- (TUIAudioRecorder *)recorder {
if (!_recorder) {
_recorder = [[TUIAudioRecorder alloc] init];
_recorder.delegate = self;
}
return _recorder;
}
@end

View File

@@ -0,0 +1,134 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
/**
* This document declares the relevant components to implement the input area.
* The input area includes the emoticon view input area (TUIFaceView+TUIMoreView), the "more" functional area (TUIMoreView) and the text input area
* (TUIInputBar). This file contains the TUIInputControllerDelegate protocol and the TInputController class. In the input bar (TUIInputBar), button response
* callbacks for expressions, voices, and more views are provided. In this class, the InputBar is actually combined with the above three views to realize the
* display and switching logic of each view.
*/
#import <TIMCommon/TUIMessageCell.h>
#import <UIKit/UIKit.h>
#import "TUIChatDefine.h"
#import "TUIFaceView.h"
#import "TUIInputBar_Minimalist.h"
#import "TUIMenuView_Minimalist.h"
#import "TUIReplyPreviewBar_Minimalist.h"
#import "TUIFaceSegementScrollView.h"
@class TUIInputController_Minimalist;
/////////////////////////////////////////////////////////////////////////////////
//
// TUIInputControllerDelegate
//
/////////////////////////////////////////////////////////////////////////////////
@protocol TUIInputControllerDelegate_Minimalist <NSObject>
/**
* Callback when the current InputController height changes.
* You can use this callback to adjust the UI layout of each component in the controller according to the changed height.
*/
- (void)inputController:(TUIInputController_Minimalist *)inputController didChangeHeight:(CGFloat)height;
/**
* Callback when the current InputController sends a message.
*/
- (void)inputController:(TUIInputController_Minimalist *)inputController didSendMessage:(V2TIMMessage *)msg;
/**
* Callback when the more button in the bottom of input controller was clicked
*/
- (void)inputControllerDidSelectMoreButton:(TUIInputController_Minimalist *)inputController;
/**
* Callback when the take-photo button in the bottom of input controller was clicked
*/
- (void)inputControllerDidSelectCamera:(TUIInputController_Minimalist *)inputController;
/**
* Callback when @ character is entered
*/
- (void)inputControllerDidInputAt:(TUIInputController_Minimalist *)inputController;
/**
* Callback when there are @xxx characters removed
*/
- (void)inputController:(TUIInputController_Minimalist *)inputController didDeleteAt:(NSString *)atText;
- (void)inputControllerBeginTyping:(TUIInputController_Minimalist *)inputController;
- (void)inputControllerEndTyping:(TUIInputController_Minimalist *)inputController;
@end
/////////////////////////////////////////////////////////////////////////////////
//
// TUIInputControllerDelegate
//
/////////////////////////////////////////////////////////////////////////////////
@interface TUIInputController_Minimalist : UIViewController
/**
* A preview view above the input box for message reply scenarios
*/
@property(nonatomic, strong) TUIReplyPreviewBar_Minimalist *replyPreviewBar;
/**
* The preview view below the input box, with the message reference scene
*
*/
@property(nonatomic, strong) TUIReferencePreviewBar_Minimalist *referencePreviewBar;
/**
* Message currently being replied to
*/
@property(nonatomic, strong) TUIReplyPreviewData *replyData;
@property(nonatomic, strong) TUIReferencePreviewData *referenceData;
/**
* Input bar
* The input bar contains a series of interactive components such as text input box, voice button, "more" button, emoticon button, etc., and provides
* corresponding callbacks for these components.
*/
@property(nonatomic, strong) TUIInputBar_Minimalist *inputBar;
/**
* Emoticon view
* The emoticon view generally appears after clicking the "Smiley" button. Responsible for displaying each expression group and the expressions within the
* group.
*
*/
@property(nonatomic, strong) TUIFaceSegementScrollView *faceSegementScrollView;
/**
* Menu view
* The menu view is located below the emoticon view and is responsible for providing the emoticon grouping unit and the send button.
*/
@property(nonatomic, strong) TUIMenuView_Minimalist *menuView;
@property(nonatomic, weak) id<TUIInputControllerDelegate_Minimalist> delegate;
/**
* Reset the current input controller.
* If there is currently an emoji view or a "more" view being displayed, collapse the corresponding view and set the current status to Input_Status_Input.
* That is, no matter what state the current InputController is in, reset it to its initialized state.
*/
- (void)reset;
/**
* Show/hide preview bar of message reply input box
*/
- (void)showReplyPreview:(TUIReplyPreviewData *)data;
- (void)showReferencePreview:(TUIReferencePreviewData *)data;
- (void)exitReplyAndReference:(void (^__nullable)(void))finishedCallback;
/**
* Current input box state
*/
@property(nonatomic, assign, readonly) InputStatus status;
@end

View File

@@ -0,0 +1,627 @@
//
// TInputController.m
// UIKit
//
// Created by kennethmiao on 2018/9/18.
// Copyright © 2018 Tencent. All rights reserved.
//
#import "TUIInputController_Minimalist.h"
#import <AVFoundation/AVFoundation.h>
#import <TIMCommon/NSString+TUIEmoji.h>
#import <TIMCommon/TIMCommonModel.h>
#import <TIMCommon/TIMDefine.h>
#import <TUICore/TUIDarkModel.h>
#import <TUICore/TUIThemeManager.h>
#import "TUIChatConfig.h"
#import "TUIChatDataProvider.h"
#import "TUIChatModifyMessageHelper.h"
#import "TUICloudCustomDataTypeCenter.h"
#import "TUIFaceMessageCell_Minimalist.h"
#import "TUIMenuCellData.h"
#import "TUIMenuCell_Minimalist.h"
#import "TUIMessageDataProvider.h"
#import "TUITextMessageCell_Minimalist.h"
#import "TUIVoiceMessageCell_Minimalist.h"
#import <TIMCommon/TIMCommonMediator.h>
#import <TIMCommon/TUIEmojiMeditorProtocol.h>
@interface TUIInputController_Minimalist () <TUIInputBarDelegate_Minimalist, TUIMenuViewDelegate_Minimalist, TUIFaceViewDelegate>
@property(nonatomic, assign) InputStatus status;
@property(nonatomic, assign) CGRect keyboardFrame;
@property(nonatomic, copy) void (^modifyRootReplyMsgBlock)(TUIMessageCellData *);
@end
@implementation TUIInputController_Minimalist
- (void)viewDidLoad {
[super viewDidLoad];
[self setupViews];
}
- (void)viewWillAppear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(inputMessageStatusChanged:) name:@"kTUINotifyMessageStatusChanged" object:nil];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
for (UIGestureRecognizer *gesture in self.view.window.gestureRecognizers) {
NSLog(@"gesture = %@", gesture);
gesture.delaysTouchesBegan = NO;
NSLog(@"delaysTouchesBegan = %@", gesture.delaysTouchesBegan ? @"YES" : @"NO");
NSLog(@"delaysTouchesEnded = %@", gesture.delaysTouchesEnded ? @"YES" : @"NO");
}
self.navigationController.interactivePopGestureRecognizer.delaysTouchesBegan = NO;
}
- (void)viewDidDisappear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)setupViews {
self.view.backgroundColor = RGBA(255, 255, 255, 1);
_status = Input_Status_Input;
_inputBar =
[[TUIInputBar_Minimalist alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(self.replyPreviewBar.frame), self.view.frame.size.width, TTextView_Height)];
_inputBar.delegate = self;
[self.view addSubview:_inputBar];
}
- (void)keyboardWillHide:(NSNotification *)notification {
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
CGFloat inputContainerBottom = [self getInputContainerBottom];
[_delegate inputController:self didChangeHeight:inputContainerBottom + Bottom_SafeHeight];
}
if (_status == Input_Status_Input_Keyboard) {
_status = Input_Status_Input;
}
}
- (void)keyboardWillShow:(NSNotification *)notification {
if (_status == Input_Status_Input_Face) {
[self hideFaceAnimation];
} else {
//[self hideFaceAnimation:NO];
//[self hideMoreAnimation:NO];
}
_status = Input_Status_Input_Keyboard;
}
- (void)keyboardWillChangeFrame:(NSNotification *)notification {
CGRect keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
CGFloat inputContainerBottom = [self getInputContainerBottom];
[_delegate inputController:self didChangeHeight:keyboardFrame.size.height + inputContainerBottom];
}
self.keyboardFrame = keyboardFrame;
}
- (void)hideFaceAnimation {
self.faceSegementScrollView.hidden = NO;
self.faceSegementScrollView.alpha = 1.0;
self.menuView.hidden = NO;
self.menuView.alpha = 1.0;
__weak typeof(self) ws = self;
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
ws.faceSegementScrollView.alpha = 0.0;
ws.menuView.alpha = 0.0;
}
completion:^(BOOL finished) {
ws.faceSegementScrollView.hidden = YES;
ws.faceSegementScrollView.alpha = 1.0;
ws.menuView.hidden = YES;
ws.menuView.alpha = 1.0;
[ws.menuView removeFromSuperview];
[ws.faceSegementScrollView removeFromSuperview];
ws.view.backgroundColor = RGBA(255, 255, 255, 1);
}];
}
- (void)showFaceAnimation {
[self.view addSubview:self.faceSegementScrollView];
[self.view addSubview:self.menuView];
__weak typeof(self) ws = self;
[self.faceSegementScrollView updateRecentView];
[self.faceSegementScrollView setAllFloatCtrlViewAllowSendSwitch:(self.inputBar.inputTextView.text.length > 0)?YES:NO];
self.faceSegementScrollView.onScrollCallback = ^(NSInteger indexPage) {
[ws.menuView scrollToMenuIndex:indexPage];
};
self.inputBar.inputBarTextChanged = ^(UITextView *textview) {
if(textview.text.length > 0) {
[ws.faceSegementScrollView setAllFloatCtrlViewAllowSendSwitch:YES];
}
else {
[ws.faceSegementScrollView setAllFloatCtrlViewAllowSendSwitch:NO];
}
};
self.faceSegementScrollView.hidden = NO;
CGRect frame = self.menuView.frame;
frame.origin.y = self.view.window.frame.size.height;
self.menuView.frame = frame;
self.menuView.hidden = NO;
frame = self.faceSegementScrollView.frame;
frame.origin.y = self.menuView.frame.origin.y + self.menuView.frame.size.height;
self.faceSegementScrollView.frame = frame;
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
CGRect newFrame = ws.menuView.frame;
newFrame.origin.y = CGRectGetMaxY(ws.inputBar.frame); // ws.inputBar.frame.origin.y + ws.inputBar.frame.size.height;
ws.menuView.frame = newFrame;
newFrame = ws.faceSegementScrollView.frame;
newFrame.origin.y = ws.menuView.frame.origin.y + ws.menuView.frame.size.height;
ws.faceSegementScrollView.frame = newFrame;
ws.view.backgroundColor = TUIChatDynamicColor(@"chat_input_controller_bg_color", @"#EBF0F6");
}
completion:nil];
}
- (void)inputBarDidTouchCamera:(TUIInputBar_Minimalist *)textView {
[_inputBar.inputTextView resignFirstResponder];
[self hideFaceAnimation];
_status = Input_Status_Input_Camera;
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
CGFloat inputContainerBottom = [self getInputContainerBottom];
[_delegate inputController:self didChangeHeight:inputContainerBottom + Bottom_SafeHeight];
}
if (_delegate && [_delegate respondsToSelector:@selector(inputControllerDidSelectCamera:)]) {
[_delegate inputControllerDidSelectCamera:self];
}
}
- (void)inputBarDidTouchMore:(TUIInputBar_Minimalist *)textView {
if (self.delegate && [self.delegate respondsToSelector:@selector(inputControllerDidSelectMoreButton:)]) {
[self.delegate inputControllerDidSelectMoreButton:self];
}
}
- (void)inputBarDidTouchFace:(TUIInputBar_Minimalist *)textView {
if ([TIMConfig defaultConfig].faceGroups.count == 0) {
return;
}
[_inputBar.inputTextView resignFirstResponder];
_status = Input_Status_Input_Face;
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
[_delegate inputController:self
didChangeHeight:CGRectGetMaxY(_inputBar.frame) + self.faceSegementScrollView.frame.size.height + self.menuView.frame.size.height ];
}
[self showFaceAnimation];
}
- (void)inputBarDidTouchKeyboard:(TUIInputBar_Minimalist *)textView {
if (_status == Input_Status_Input_Face) {
[self hideFaceAnimation];
}
_status = Input_Status_Input_Keyboard;
[_inputBar.inputTextView becomeFirstResponder];
}
- (void)inputBar:(TUIInputBar_Minimalist *)textView didChangeInputHeight:(CGFloat)offset {
if (_status == Input_Status_Input_Face) {
[self showFaceAnimation];
}
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
[_delegate inputController:self didChangeHeight:self.view.frame.size.height + offset];
if (_referencePreviewBar) {
CGRect referencePreviewBarFrame = _referencePreviewBar.frame;
_referencePreviewBar.frame = CGRectMake(referencePreviewBarFrame.origin.x, referencePreviewBarFrame.origin.y + offset,
referencePreviewBarFrame.size.width, referencePreviewBarFrame.size.height);
}
}
}
- (void)inputBar:(TUIInputBar_Minimalist *)textView didSendText:(NSString *)text {
/**
* Emoticon internationalization --> restore to actual Chinese key
*/
NSString *content = [text getInternationalStringWithfaceContent];
V2TIMMessage *message = [[V2TIMManager sharedInstance] createTextMessage:content];
[self appendReplyDataIfNeeded:message];
[self appendReferenceDataIfNeeded:message];
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didSendMessage:)]) {
[_delegate inputController:self didSendMessage:message];
}
}
- (void)inputMessageStatusChanged:(NSNotification *)noti {
NSDictionary *userInfo = noti.userInfo;
TUIMessageCellData *msg = userInfo[@"msg"];
long status = [userInfo[@"status"] intValue];
if ([msg isKindOfClass:TUIMessageCellData.class] && status == Msg_Status_Succ) {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.modifyRootReplyMsgBlock) {
self.modifyRootReplyMsgBlock(msg);
}
});
}
}
- (void)appendReplyDataIfNeeded:(V2TIMMessage *)message {
if (self.replyData) {
V2TIMMessage *parentMsg = self.replyData.originMessage;
NSMutableDictionary *simpleReply = [NSMutableDictionary dictionary];
[simpleReply addEntriesFromDictionary:@{
@"messageID" : self.replyData.msgID ?: @"",
@"messageAbstract" : [self.replyData.msgAbstract ?: @"" getInternationalStringWithfaceContent],
@"messageSender" : self.replyData.sender ?: @"",
@"messageType" : @(self.replyData.type),
@"messageTime" : @(self.replyData.originMessage.timestamp ? [self.replyData.originMessage.timestamp timeIntervalSince1970] : 0),
@"messageSequence" : @(self.replyData.originMessage.seq),
@"version" : @(kMessageReplyVersion),
}];
NSMutableDictionary *cloudResultDic = [[NSMutableDictionary alloc] initWithCapacity:5];
if (parentMsg.cloudCustomData) {
NSDictionary *originDic = [TUITool jsonData2Dictionary:parentMsg.cloudCustomData];
if (originDic && [originDic isKindOfClass:[NSDictionary class]]) {
[cloudResultDic addEntriesFromDictionary:originDic];
}
/**
* Accept the data in the parent, but cannot save messageReplies\messageReact, because the root message topic creator has this field.
* messageReplies\messageReact cannot be stored in the new message currently sent
*/
[cloudResultDic removeObjectForKey:@"messageReplies"];
[cloudResultDic removeObjectForKey:@"messageReact"];
}
NSString *messageParentReply = cloudResultDic[@"messageReply"];
NSString *messageRootID = [messageParentReply valueForKey:@"messageRootID"];
if (self.replyData.messageRootID.length > 0) {
messageRootID = self.replyData.messageRootID;
}
if (!IS_NOT_EMPTY_NSSTRING(messageRootID)) {
/**
* If the original message does not have a messageRootID, you need to use the msgID of the current original message as root
*/
if (IS_NOT_EMPTY_NSSTRING(parentMsg.msgID)) {
messageRootID = parentMsg.msgID;
}
}
[simpleReply setObject:messageRootID forKey:@"messageRootID"];
[cloudResultDic setObject:simpleReply forKey:@"messageReply"];
NSData *data = [TUITool dictionary2JsonData:cloudResultDic];
if (data) {
message.cloudCustomData = data;
}
[self exitReplyAndReference:nil];
__weak typeof(self) weakSelf = self;
self.modifyRootReplyMsgBlock = ^(TUIMessageCellData *cellData) {
__strong typeof(self) strongSelf = weakSelf;
[strongSelf modifyRootReplyMsgByID:messageRootID currentMsg:cellData];
strongSelf.modifyRootReplyMsgBlock = nil;
};
}
}
- (void)modifyRootReplyMsgByID:(NSString *)messageRootID currentMsg:(TUIMessageCellData *)messageCellData {
NSDictionary *simpleCurrentContent = @{
@"messageID" : messageCellData.innerMessage.msgID ?: @"",
@"messageAbstract" : [messageCellData.innerMessage.textElem.text ?: @"" getInternationalStringWithfaceContent],
@"messageSender" : messageCellData.innerMessage.sender ?: @"",
@"messageType" : @(messageCellData.innerMessage.elemType),
@"messageTime" : @(messageCellData.innerMessage.timestamp ? [messageCellData.innerMessage.timestamp timeIntervalSince1970] : 0),
@"messageSequence" : @(messageCellData.innerMessage.seq),
@"version" : @(kMessageReplyVersion)
};
if (messageRootID) {
[TUIChatDataProvider findMessages:@[ messageRootID ]
callback:^(BOOL succ, NSString *_Nonnull error_message, NSArray *_Nonnull msgs) {
if (succ) {
if (msgs.count > 0) {
V2TIMMessage *rootMsg = msgs.firstObject;
[[TUIChatModifyMessageHelper defaultHelper] modifyMessage:rootMsg
simpleCurrentContent:simpleCurrentContent];
}
}
}];
}
}
- (void)appendReferenceDataIfNeeded:(V2TIMMessage *)message {
if (self.referenceData) {
NSDictionary *dict = @{
@"messageReply" : @{
@"messageID" : self.referenceData.msgID ?: @"",
@"messageAbstract" : [self.referenceData.msgAbstract ?: @"" getInternationalStringWithfaceContent],
@"messageSender" : self.referenceData.sender ?: @"",
@"messageType" : @(self.referenceData.type),
@"messageTime" : @(self.referenceData.originMessage.timestamp ? [self.referenceData.originMessage.timestamp timeIntervalSince1970] : 0),
@"messageSequence" : @(self.referenceData.originMessage.seq),
@"version" : @(kMessageReplyVersion)
}
};
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:0 error:&error];
if (error == nil) {
message.cloudCustomData = data;
}
[self exitReplyAndReference:nil];
}
}
- (void)inputBar:(TUIInputBar_Minimalist *)textView didSendVoice:(NSString *)path {
NSURL *url = [NSURL fileURLWithPath:path];
AVURLAsset *audioAsset = [AVURLAsset URLAssetWithURL:url options:nil];
float duration = (float)CMTimeGetSeconds(audioAsset.duration);
int formatDuration = duration > 59 ? 60 : duration + 1 ;
V2TIMMessage *message = [[V2TIMManager sharedInstance] createSoundMessage:path duration:formatDuration];
if (message && _delegate && [_delegate respondsToSelector:@selector(inputController:didSendMessage:)]) {
[_delegate inputController:self didSendMessage:message];
}
}
- (void)inputBarDidInputAt:(TUIInputBar_Minimalist *)textView {
if (_delegate && [_delegate respondsToSelector:@selector(inputControllerDidInputAt:)]) {
[_delegate inputControllerDidInputAt:self];
}
}
- (void)inputBar:(TUIInputBar_Minimalist *)textView didDeleteAt:(NSString *)atText {
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didDeleteAt:)]) {
[_delegate inputController:self didDeleteAt:atText];
}
}
- (void)inputBarDidDeleteBackward:(TUIInputBar_Minimalist *)textView {
if (textView.inputTextView.text.length == 0) {
[self exitReplyAndReference:nil];
}
}
- (void)inputTextViewShouldBeginTyping:(UITextView *)textView {
if (_delegate && [_delegate respondsToSelector:@selector(inputControllerBeginTyping:)]) {
[_delegate inputControllerBeginTyping:self];
}
}
- (void)inputTextViewShouldEndTyping:(UITextView *)textView {
if (_delegate && [_delegate respondsToSelector:@selector(inputControllerEndTyping:)]) {
[_delegate inputControllerEndTyping:self];
}
}
- (void)reset {
if (_status == Input_Status_Input) {
return;
} else if (_status == Input_Status_Input_Face) {
[self hideFaceAnimation];
}
_status = Input_Status_Input;
[_inputBar.inputTextView resignFirstResponder];
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
CGFloat inputContainerBottom = [self getInputContainerBottom];
[_delegate inputController:self didChangeHeight:inputContainerBottom + Bottom_SafeHeight];
}
}
- (void)showReferencePreview:(TUIReferencePreviewData *)data {
self.referenceData = data;
[self.referencePreviewBar removeFromSuperview];
[self.view addSubview:self.referencePreviewBar];
self.inputBar.lineView.hidden = YES;
self.referencePreviewBar.previewReferenceData = data;
self.inputBar.mm_y = 0;
self.referencePreviewBar.frame = CGRectMake(0, 0, self.view.bounds.size.width, TMenuView_Menu_Height);
self.referencePreviewBar.mm_y = CGRectGetMaxY(self.inputBar.frame);
// Set the default position to solve the UI confusion when the keyboard does not become the first responder
if (self.delegate && [self.delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
[self.delegate inputController:self didChangeHeight:CGRectGetMaxY(self.inputBar.frame) + Bottom_SafeHeight + TMenuView_Menu_Height];
}
if (self.status == Input_Status_Input_Keyboard) {
CGFloat keyboradHeight = self.keyboardFrame.size.height;
if (self.delegate && [self.delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
[self.delegate inputController:self didChangeHeight:CGRectGetMaxY(self.referencePreviewBar.frame) + keyboradHeight];
}
} else if (self.status == Input_Status_Input_Face) {
[self.inputBar changeToKeyboard];
} else {
[self.inputBar.inputTextView becomeFirstResponder];
}
}
- (void)showReplyPreview:(TUIReplyPreviewData *)data {
self.replyData = data;
[self.replyPreviewBar removeFromSuperview];
[self.view addSubview:self.replyPreviewBar];
self.inputBar.lineView.hidden = YES;
self.replyPreviewBar.previewData = data;
self.replyPreviewBar.frame = CGRectMake(0, 0, self.view.bounds.size.width, TMenuView_Menu_Height);
self.inputBar.mm_y = CGRectGetMaxY(self.replyPreviewBar.frame);
// Set the default position to solve the UI confusion when the keyboard does not become the first responder
if (self.delegate && [self.delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
[self.delegate inputController:self didChangeHeight:CGRectGetMaxY(self.inputBar.frame) + Bottom_SafeHeight];
}
if (self.status == Input_Status_Input_Keyboard) {
CGFloat keyboradHeight = self.keyboardFrame.size.height;
if (self.delegate && [self.delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
[self.delegate inputController:self didChangeHeight:CGRectGetMaxY(self.inputBar.frame) + keyboradHeight];
}
} else if (self.status == Input_Status_Input_Face) {
[self.inputBar changeToKeyboard];
} else {
[self.inputBar.inputTextView becomeFirstResponder];
}
}
- (void)exitReplyAndReference:(void (^__nullable)(void))finishedCallback {
if (self.replyData == nil && self.referenceData == nil) {
if (finishedCallback) {
finishedCallback();
}
return;
}
self.replyData = nil;
self.referenceData = nil;
__weak typeof(self) weakSelf = self;
[UIView animateWithDuration:0.25
animations:^{
weakSelf.replyPreviewBar.hidden = YES;
weakSelf.referencePreviewBar.hidden = YES;
weakSelf.inputBar.mm_y = 0;
if (weakSelf.status == Input_Status_Input_Keyboard) {
CGFloat keyboradHeight = weakSelf.keyboardFrame.size.height;
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
[weakSelf.delegate inputController:weakSelf didChangeHeight:CGRectGetMaxY(weakSelf.inputBar.frame) + keyboradHeight];
}
} else {
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
[weakSelf.delegate inputController:weakSelf didChangeHeight:CGRectGetMaxY(weakSelf.inputBar.frame) + Bottom_SafeHeight];
}
}
}
completion:^(BOOL finished) {
[weakSelf.replyPreviewBar removeFromSuperview];
[weakSelf.referencePreviewBar removeFromSuperview];
weakSelf.replyPreviewBar = nil;
weakSelf.referencePreviewBar = nil;
[weakSelf hideFaceAnimation];
weakSelf.inputBar.lineView.hidden = NO;
if (finishedCallback) {
finishedCallback();
}
}];
}
- (void)menuView:(TUIMenuView_Minimalist *)menuView didSelectItemAtIndex:(NSInteger)index {
[self.faceSegementScrollView setPageIndex:index];
}
- (void)menuViewDidSendMessage:(TUIMenuView_Minimalist *)menuView {
NSString *text = [_inputBar getInput];
if ([text isEqualToString:@""]) {
return;
}
/**
* Emoticon internationalization --> restore to actual Chinese key
*/
NSString *content = [text getInternationalStringWithfaceContent];
[_inputBar clearInput];
V2TIMMessage *message = [[V2TIMManager sharedInstance] createTextMessage:content];
[self appendReplyDataIfNeeded:message];
[self appendReferenceDataIfNeeded:message];
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didSendMessage:)]) {
[_delegate inputController:self didSendMessage:message];
}
}
- (void)faceView:(TUIFaceView *)faceView scrollToFaceGroupIndex:(NSInteger)index {
[self.menuView scrollToMenuIndex:index];
}
- (void)faceViewDidBackDelete:(TUIFaceView *)faceView {
[_inputBar backDelete];
}
- (void)faceViewClickSendMessageBtn {
[self menuViewDidSendMessage:self.menuView];
}
- (void)faceView:(TUIFaceView *)faceView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
TUIFaceGroup *group = faceView.faceGroups[indexPath.section];
TUIFaceCellData *face = group.faces[indexPath.row];
if (group.isNeedAddInInputBar) {
[_inputBar addEmoji:face];
[self updateRecentMenuQueue:face.name];
} else {
if (face.name) {
V2TIMMessage *message = [[V2TIMManager sharedInstance] createFaceMessage:group.groupIndex data:[face.name dataUsingEncoding:NSUTF8StringEncoding]];
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didSendMessage:)]) {
[_delegate inputController:self didSendMessage:message];
}
}
}
}
- (void)updateRecentMenuQueue:(NSString *)faceName {
id<TUIEmojiMeditorProtocol> service = [[TIMCommonMediator share] getObject:@protocol(TUIEmojiMeditorProtocol)];
return [service updateRecentMenuQueue:faceName];
}
#pragma mark - lazy load
- (TUIFaceSegementScrollView *)faceSegementScrollView {
if(!_faceSegementScrollView) {
_faceSegementScrollView = [[TUIFaceSegementScrollView alloc]
initWithFrame:CGRectMake(0,
_inputBar.frame.origin.y + _inputBar.frame.size.height, self.view.frame.size.width,
TFaceView_Height)];
[_faceSegementScrollView setItems:[TIMConfig defaultConfig].faceGroups delegate:self];
}
return _faceSegementScrollView;
}
- (TUIMenuView_Minimalist *)menuView {
if (!_menuView) {
_menuView = [[TUIMenuView_Minimalist alloc] initWithFrame:CGRectMake(16, _inputBar.mm_maxY, self.view.frame.size.width - 32, TMenuView_Menu_Height)];
_menuView.delegate = self;
TIMConfig *config = [TIMConfig defaultConfig];
NSMutableArray *menus = [NSMutableArray array];
for (NSInteger i = 0; i < config.faceGroups.count; ++i) {
TUIFaceGroup *group = config.faceGroups[i];
TUIMenuCellData *data = [[TUIMenuCellData alloc] init];
data.path = group.menuPath;
data.isSelected = NO;
if (i == 0) {
data.isSelected = YES;
}
[menus addObject:data];
}
[_menuView setData:menus];
}
return _menuView;
}
- (TUIReplyPreviewBar_Minimalist *)replyPreviewBar {
if (_replyPreviewBar == nil) {
_replyPreviewBar = [[TUIReplyPreviewBar_Minimalist alloc] init];
__weak typeof(self) weakSelf = self;
_replyPreviewBar.onClose = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf exitReplyAndReference:nil];
};
}
return _replyPreviewBar;
}
- (TUIReferencePreviewBar_Minimalist *)referencePreviewBar {
if (_referencePreviewBar == nil) {
_referencePreviewBar = [[TUIReferencePreviewBar_Minimalist alloc] init];
__weak typeof(self) weakSelf = self;
_referencePreviewBar.onClose = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf exitReplyAndReference:nil];
};
}
return _referencePreviewBar;
}
- (CGFloat)getInputContainerBottom {
CGFloat inputHeight = CGRectGetMaxY(_inputBar.frame);
if (_referencePreviewBar) {
inputHeight = CGRectGetMaxY(_referencePreviewBar.frame);
}
return inputHeight;
}
@end

View File

@@ -0,0 +1,62 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
/**
*
* This file declares the components used to implement the emoji menu view.
* The emoji menu view, the bright white view at the bottom of the emoji view, is responsible for displaying individual emoji groups and their thumbnails, and
* providing a "Send" button.
*
* The TUIMenuViewDelegate protocol provides the emoticon menu view with event callbacks for sending messages and cell selection.
* The TUIMenuView class, the "ontology" of the emoticon menu view, is responsible for displaying it in the form of a view in the UI, and at the same time
* serving as a "container" for each component. You can switch between different groups of emoticons or send emoticons through the emoticon menu view.
*/
#import <UIKit/UIKit.h>
#import "TUIMenuCellData.h"
@class TUIMenuView_Minimalist;
/////////////////////////////////////////////////////////////////////////////////
//
// TUIMenuViewDelegate
//
/////////////////////////////////////////////////////////////////////////////////
@protocol TUIMenuViewDelegate_Minimalist <NSObject>
/**
* Callback after clicking on a specific menuCell
* You can use this callback to achieve: in response to the user's click, switch to the corresponding emoticon group view according to the menuCell selected by
* the user.
*/
- (void)menuView:(TUIMenuView_Minimalist *)menuView didSelectItemAtIndex:(NSInteger)index;
/**
* Callback after click of send button on menuView
* You can send the content of the current input box (TUIInputBar) through this callback
* In the default implementation of TUIKit, the delegate call chain is menuView -> inputController -> messageController.
* Call the sendMessage function in the above classes respectively, so that the functions are reasonably layered and the code reuse rate is improved.
*/
- (void)menuViewDidSendMessage:(TUIMenuView_Minimalist *)menuView;
@end
/////////////////////////////////////////////////////////////////////////////////
//
// TUIMenuView
//
/////////////////////////////////////////////////////////////////////////////////
///
@interface TUIMenuView_Minimalist : UIView
@property(nonatomic, strong) UICollectionView *menuCollectionView;
@property(nonatomic, strong) UICollectionViewFlowLayout *menuFlowLayout;
@property(nonatomic, weak) id<TUIMenuViewDelegate_Minimalist> delegate;
- (void)scrollToMenuIndex:(NSInteger)index;
- (void)setData:(NSMutableArray<TUIMenuCellData *> *)data;
@end

View File

@@ -0,0 +1,122 @@
//
// MenuView.m
// UIKit
//
// Created by kennethmiao on 2018/9/18.
// Copyright © 2018 Tencent. All rights reserved.
//
#import "TUIMenuView_Minimalist.h"
#import <TIMCommon/TIMDefine.h>
#import <TUICore/TUIDarkModel.h>
#import <TUICore/TUIGlobalization.h>
#import <TUICore/TUIThemeManager.h>
#import "TUIMenuCell_Minimalist.h"
@interface TUIMenuView_Minimalist () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property(nonatomic, strong) NSMutableArray<TUIMenuCellData *> *data;
@end
@implementation TUIMenuView_Minimalist
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupViews];
[self defaultLayout];
}
return self;
}
- (void)setData:(NSMutableArray<TUIMenuCellData *> *)data {
_data = data;
[_menuCollectionView reloadData];
[self defaultLayout];
[_menuCollectionView layoutIfNeeded];
[_menuCollectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0] animated:NO scrollPosition:UICollectionViewScrollPositionNone];
}
- (void)setupViews {
self.backgroundColor = [UIColor clearColor];
_menuFlowLayout = [[TUICollectionRTLFitFlowLayout alloc] init];
_menuFlowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_menuFlowLayout.minimumLineSpacing = 0;
_menuFlowLayout.minimumInteritemSpacing = 0;
//_menuFlowLayout.headerReferenceSize = CGSizeMake(TMenuView_Margin, 1);
_menuCollectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:_menuFlowLayout];
[_menuCollectionView registerClass:[TUIMenuCell_Minimalist class] forCellWithReuseIdentifier:TMenuCell_ReuseId];
[_menuCollectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:TMenuCell_Line_ReuseId];
_menuCollectionView.collectionViewLayout = _menuFlowLayout;
_menuCollectionView.delegate = self;
_menuCollectionView.dataSource = self;
_menuCollectionView.showsHorizontalScrollIndicator = NO;
_menuCollectionView.showsVerticalScrollIndicator = NO;
_menuCollectionView.backgroundColor = self.backgroundColor;
_menuCollectionView.alwaysBounceHorizontal = YES;
[self addSubview:_menuCollectionView];
}
- (void)defaultLayout {
[_menuCollectionView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(0);
make.trailing.mas_equalTo(self.mas_trailing).mas_offset(0);
make.height.mas_equalTo(40);
make.centerY.mas_equalTo(self);
}];
}
- (void)sendUpInside:(UIButton *)sender {
if (_delegate && [_delegate respondsToSelector:@selector(menuViewDidSendMessage:)]) {
[_delegate menuViewDidSendMessage:self];
}
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return _data.count * 2;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row % 2 == 0) {
TUIMenuCell_Minimalist *cell = [collectionView dequeueReusableCellWithReuseIdentifier:TMenuCell_ReuseId forIndexPath:indexPath];
[cell setData:_data[indexPath.row / 2]];
return cell;
} else {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:TMenuCell_Line_ReuseId forIndexPath:indexPath];
cell.backgroundColor = [UIColor clearColor];
return cell;
}
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row % 2 != 0) {
return;
}
for (NSInteger i = 0; i < _data.count; ++i) {
TUIMenuCellData *data = _data[i];
data.isSelected = (i == indexPath.row / 2);
}
[_menuCollectionView reloadData];
if (_delegate && [_delegate respondsToSelector:@selector(menuView:didSelectItemAtIndex:)]) {
[_delegate menuView:self didSelectItemAtIndex:indexPath.row / 2];
}
}
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row % 2 == 0) {
CGFloat wh = collectionView.frame.size.height;
return CGSizeMake(wh, wh);
} else {
return CGSizeMake(TLine_Heigh, collectionView.frame.size.height);
}
}
- (void)scrollToMenuIndex:(NSInteger)index {
for (NSInteger i = 0; i < _data.count; ++i) {
TUIMenuCellData *data = _data[i];
data.isSelected = (i == index);
}
[_menuCollectionView reloadData];
}
@end

View File

@@ -0,0 +1,26 @@
//
// TUIInputPreviewBar.h
// TUIChat
//
// Created by harvy on 2021/11/9.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "TUIReplyPreviewData.h"
NS_ASSUME_NONNULL_BEGIN
@interface TUIReplyPreviewBar_Minimalist : UIView
@property(nonatomic, strong) UILabel *titleLabel;
@property(nonatomic, strong) UIButton *closeButton;
@property(nonatomic, copy) TUIInputPreviewBarCallback onClose;
@property(nonatomic, strong) TUIReplyPreviewData *previewData;
@property(nonatomic, strong) TUIReferencePreviewData *previewReferenceData;
@end
@interface TUIReferencePreviewBar_Minimalist : TUIReplyPreviewBar_Minimalist
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,100 @@
//
// TUIInputPreviewBar.m
// TUIChat
//
// Created by harvy on 2021/11/9.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIReplyPreviewBar_Minimalist.h"
#import <TIMCommon/NSString+TUIEmoji.h>
#import <TIMCommon/TIMDefine.h>
#import <TUICore/TUIDarkModel.h>
#import <TUICore/TUIThemeManager.h>
@implementation TUIReplyPreviewBar_Minimalist
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setupViews];
}
return self;
}
- (void)setupViews {
self.backgroundColor = TUIChatDynamicColor(@"chat_input_controller_bg_color", @"#EBF0F6");
[self addSubview:self.titleLabel];
[self addSubview:self.closeButton];
}
- (void)layoutSubviews {
[super layoutSubviews];
self.closeButton.mm_width(16).mm_height(16);
self.closeButton.mm_centerY = self.mm_centerY;
self.closeButton.mm_right(16.0);
self.titleLabel.mm_x = 16.0;
self.titleLabel.mm_y = 10;
self.titleLabel.mm_w = self.closeButton.mm_x - 10 - 16;
self.titleLabel.mm_h = self.mm_h - 20;
}
- (void)onClose:(UIButton *)closeButton {
if (self.onClose) {
self.onClose();
}
}
- (void)setPreviewData:(TUIReplyPreviewData *)previewData {
_previewData = previewData;
NSString *abstract = [TUIReplyPreviewData displayAbstract:previewData.type abstract:previewData.msgAbstract withFileName:YES isRisk:NO];
_titleLabel.text = [[NSString stringWithFormat:@"%@: %@", previewData.sender, abstract] getLocalizableStringWithFaceContent];
_titleLabel.lineBreakMode = previewData.type == (NSInteger)V2TIM_ELEM_TYPE_FILE ? NSLineBreakByTruncatingMiddle : NSLineBreakByTruncatingTail;
}
- (void)setPreviewReferenceData:(TUIReferencePreviewData *)previewReferenceData {
_previewReferenceData = previewReferenceData;
NSString *abstract = [TUIReferencePreviewData displayAbstract:previewReferenceData.type
abstract:previewReferenceData.msgAbstract
withFileName:YES isRisk:NO];
_titleLabel.text = [[NSString stringWithFormat:@"%@: %@", previewReferenceData.sender, abstract] getLocalizableStringWithFaceContent];
_titleLabel.lineBreakMode = previewReferenceData.type == (NSInteger)V2TIM_ELEM_TYPE_FILE ? NSLineBreakByTruncatingMiddle : NSLineBreakByTruncatingTail;
}
- (UILabel *)titleLabel {
if (_titleLabel == nil) {
_titleLabel = [[UILabel alloc] init];
_titleLabel.font = [UIFont systemFontOfSize:16];
_titleLabel.textColor = [UIColor colorWithRed:143 / 255.0 green:150 / 255.0 blue:160 / 255.0 alpha:1 / 1.0];
}
return _titleLabel;
}
- (UIButton *)closeButton {
if (_closeButton == nil) {
_closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_closeButton setImage:TUIChatCommonBundleImage(@"icon_close") forState:UIControlStateNormal];
[_closeButton addTarget:self action:@selector(onClose:) forControlEvents:UIControlEventTouchUpInside];
[_closeButton sizeToFit];
}
return _closeButton;
}
@end
@implementation TUIReferencePreviewBar_Minimalist
- (void)layoutSubviews {
[super layoutSubviews];
self.closeButton.mm_right(16.0);
self.closeButton.frame = CGRectMake(self.closeButton.frame.origin.x, (self.frame.size.height - 16) * 0.5, 16, 16);
self.titleLabel.mm_x = 16.0;
self.titleLabel.mm_y = 10;
self.titleLabel.mm_w = self.closeButton.mm_x - 10 - 16;
self.titleLabel.mm_h = self.mm_h - 20;
}
@end

View File

@@ -0,0 +1,21 @@
//
// TResponderTextView_Minimalist.h
// TUIKit
//
// Created by kennethmiao on 2018/10/25.
// Copyright © 2018 Tencent. All rights reserved.
//
#import <UIKit/UIKit.h>
@class TUIResponderTextView_Minimalist;
@protocol TUIResponderTextViewDelegate_Minimalist <UITextViewDelegate>
- (void)onDeleteBackward:(TUIResponderTextView_Minimalist *)textView;
@end
@interface TUIResponderTextView_Minimalist : UITextView
@property(nonatomic, weak) UIResponder *overrideNextResponder;
@end

View File

@@ -0,0 +1,64 @@
//
// TUIResponderTextView_Minimalist.m
// TUIKit
//
// Created by kennethmiao on 2018/10/25.
// Copyright © 2018 Tencent. All rights reserved.
//
#import "TUIResponderTextView_Minimalist.h"
#import <TIMCommon/NSString+TUIEmoji.h>
@implementation TUIResponderTextView_Minimalist
- (UIResponder *)nextResponder {
if (_overrideNextResponder == nil) {
return [super nextResponder];
} else {
return _overrideNextResponder;
}
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if (_overrideNextResponder != nil)
return NO;
else
return [super canPerformAction:action withSender:sender];
}
- (void)buildMenuWithBuilder:(id<UIMenuBuilder>)builder API_AVAILABLE(ios(13.0)) {
if (@available(iOS 16.0, *)) {
[builder removeMenuForIdentifier:UIMenuLookup];
}
[super buildMenuWithBuilder:builder];
}
- (void)deleteBackward {
id<TUIResponderTextViewDelegate_Minimalist> delegate = (id<TUIResponderTextViewDelegate_Minimalist>)self.delegate;
if ([delegate respondsToSelector:@selector(onDeleteBackward:)]) {
[delegate onDeleteBackward:self];
}
[super deleteBackward];
}
- (void)setText:(NSString *)text {
[super setText:text];
if (self.delegate && [self.delegate respondsToSelector:@selector(textViewDidChange:)]) {
[self.delegate textViewDidChange:self];
}
}
- (void)copy:(__unused id)sender {
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.string = [[self.textStorage attributedSubstringFromRange:self.selectedRange] tui_getPlainString];
}
- (void)cut:(nullable id)sender {
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.string = [[self.textStorage attributedSubstringFromRange:self.selectedRange] tui_getPlainString];
UIFont *textFont = [UIFont systemFontOfSize:16.0];
NSAttributedString *spaceString = [[NSAttributedString alloc] initWithString:@"" attributes:@{NSFontAttributeName : textFont}];
[self.textStorage replaceCharactersInRange:self.selectedRange withAttributedString:spaceString];
}
@end