Files
midi_ios/TUIKit/TUIChat/UI_Classic/Input/TUIInputBar.m

747 lines
32 KiB
Mathematica
Raw Normal View History

2025-08-14 10:07:49 +08:00
//
// TUIInputBar.m
// UIKit
//
// Created by kennethmiao on 2018/9/18.
// Copyright © 2018 Tencent. All rights reserved.
//
#import "TUIInputBar.h"
#import <TIMCommon/TIMDefine.h>
#import <TUICore/TUITool.h>
#import "TUIRecordView.h"
#import <TIMCommon/NSString+TUIEmoji.h>
#import <TIMCommon/NSTimer+TUISafe.h>
#import <TUICore/TUICore.h>
#import <TUICore/TUIDarkModel.h>
#import <TUICore/TUIGlobalization.h>
#import <TUICore/UIView+TUILayout.h>
#import "ReactiveObjC/ReactiveObjC.h"
#import "TUIAudioRecorder.h"
#import "TUIChatConfig.h"
@interface TUIInputBar () <UITextViewDelegate, TUIAudioRecorderDelegate>
@property(nonatomic, strong) TUIRecordView *recordView;
@property(nonatomic, strong) NSDate *recordStartTime;
@property(nonatomic, strong) TUIAudioRecorder *recorder;
@property(nonatomic, assign) BOOL isFocusOn;
@property(nonatomic, strong) NSTimer *sendTypingStatusTimer;
@property(nonatomic, assign) BOOL allowSendTypingStatusByChangeWord;
@end
@implementation TUIInputBar
- (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)dealloc {
if (_sendTypingStatusTimer) {
[_sendTypingStatusTimer invalidate];
_sendTypingStatusTimer = nil;
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - UI
- (void)setupViews {
self.backgroundColor = TUIChatDynamicColor(@"chat_input_controller_bg_color", @"#EBF0F6");
_lineView = [[UIView alloc] init];
_lineView.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#FFFFFF");
[self addSubview:_lineView];
_micButton = [[UIButton alloc] init];
[_micButton addTarget:self action:@selector(onMicButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[_micButton setImage:TUIChatBundleThemeImage(@"chat_ToolViewInputVoice_img", @"ToolViewInputVoice") forState:UIControlStateNormal];
[_micButton setImage:TUIChatBundleThemeImage(@"chat_ToolViewInputVoiceHL_img", @"ToolViewInputVoiceHL") forState:UIControlStateHighlighted];
[self addSubview:_micButton];
_faceButton = [[UIButton alloc] init];
[_faceButton addTarget:self action:@selector(onFaceEmojiButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[_faceButton setImage:TUIChatBundleThemeImage(@"chat_ToolViewEmotion_img", @"ToolViewEmotion") forState:UIControlStateNormal];
[_faceButton setImage:TUIChatBundleThemeImage(@"chat_ToolViewEmotionHL_img", @"ToolViewEmotionHL") forState:UIControlStateHighlighted];
[self addSubview:_faceButton];
_keyboardButton = [[UIButton alloc] init];
[_keyboardButton addTarget:self action:@selector(onKeyboardButtonClicked:) 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];
_moreButton = [[UIButton alloc] init];
[_moreButton addTarget:self action:@selector(onMoreButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[_moreButton setImage:TUIChatBundleThemeImage(@"chat_TypeSelectorBtn_Black_img", @"TypeSelectorBtn_Black") forState:UIControlStateNormal];
[_moreButton setImage:TUIChatBundleThemeImage(@"chat_TypeSelectorBtnHL_Black_img", @"TypeSelectorBtnHL_Black") forState:UIControlStateHighlighted];
[self addSubview:_moreButton];
_recordButton = [[UIButton alloc] init];
[_recordButton.titleLabel setFont:[UIFont systemFontOfSize:15.0f]];
[_recordButton addTarget:self action:@selector(onRecordButtonTouchDown:) forControlEvents:UIControlEventTouchDown];
[_recordButton addTarget:self action:@selector(onRecordButtonTouchUpInside:) forControlEvents:UIControlEventTouchUpInside];
[_recordButton addTarget:self action:@selector(onRecordButtonTouchCancel:) forControlEvents:UIControlEventTouchUpOutside | UIControlEventTouchCancel];
[_recordButton addTarget:self action:@selector(onRecordButtonTouchDragExit:) forControlEvents:UIControlEventTouchDragExit];
[_recordButton addTarget:self action:@selector(onRecordButtonTouchDragEnter:) forControlEvents:UIControlEventTouchDragEnter];
[_recordButton setTitle:TIMCommonLocalizableString(TUIKitInputHoldToTalk) forState:UIControlStateNormal];
[_recordButton setTitleColor:TUIChatDynamicColor(@"chat_input_text_color", @"#000000") forState:UIControlStateNormal];
_recordButton.hidden = YES;
[self addSubview:_recordButton];
_inputTextView = [[TUIResponderTextView alloc] init];
_inputTextView.delegate = self;
[_inputTextView setFont:kTUIInputNoramlFont];
_inputTextView.backgroundColor = TUIChatDynamicColor(@"chat_input_bg_color", @"#FFFFFF");
_inputTextView.textColor = TUIChatDynamicColor(@"chat_input_text_color", @"#000000");
_inputTextView.textAlignment = isRTL()?NSTextAlignmentRight: NSTextAlignmentLeft;
[_inputTextView setReturnKeyType:UIReturnKeySend];
[self addSubview:_inputTextView];
[self applyBorderTheme];
}
- (void)onThemeChanged {
[self applyBorderTheme];
}
- (void)applyBorderTheme {
if (_recordButton) {
[_recordButton.layer setMasksToBounds:YES];
[_recordButton.layer setCornerRadius:4.0f];
[_recordButton.layer setBorderWidth:1.0f];
[_recordButton.layer setBorderColor:TIMCommonDynamicColor(@"separator_color", @"#DBDBDB").CGColor];
}
if (_inputTextView) {
[_inputTextView.layer setMasksToBounds:YES];
[_inputTextView.layer setCornerRadius:4.0f];
[_inputTextView.layer setBorderWidth:0.5f];
[_inputTextView.layer setBorderColor:TIMCommonDynamicColor(@"separator_color", @"#DBDBDB").CGColor];
}
}
- (void)defaultLayout {
[_lineView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.width.mas_equalTo(self);
make.height.mas_equalTo(TLine_Heigh);
}];
CGSize buttonSize = TTextView_Button_Size;
CGFloat buttonOriginY = (TTextView_Height - buttonSize.height) * 0.5;
[_micButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(self.mas_leading);
make.centerY.mas_equalTo(self);
make.size.mas_equalTo(buttonSize);
}];
[_keyboardButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(_micButton);
}];
[_moreButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.trailing.mas_equalTo(self.mas_trailing).mas_offset(0);
make.size.mas_equalTo(buttonSize);
make.centerY.mas_equalTo(self);
}];
[_faceButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.trailing.mas_equalTo(_moreButton.mas_leading).mas_offset(- TTextView_Margin);
make.size.mas_equalTo(buttonSize);
make.centerY.mas_equalTo(self);
}];
[_recordButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(_micButton.mas_trailing).mas_offset(10);
make.trailing.mas_equalTo(_faceButton.mas_leading).mas_offset(-10);;
make.height.mas_equalTo(TTextView_TextView_Height_Min);
make.centerY.mas_equalTo(self);
}];
[_inputTextView mas_remakeConstraints:^(MASConstraintMaker *make) {
if (self.isFromReplyPage) {
make.leading.mas_equalTo(self.mas_leading).mas_offset(10);
}
else {
make.leading.mas_equalTo(_micButton.mas_trailing).mas_offset(10);
}
make.trailing.mas_equalTo(_faceButton.mas_leading).mas_offset(-10);;
make.height.mas_equalTo(TTextView_TextView_Height_Min);
make.centerY.mas_equalTo(self);
}];
}
- (void)layoutButton:(CGFloat)height {
CGRect frame = self.frame;
CGFloat offset = height - frame.size.height;
frame.size.height = height;
self.frame = frame;
CGSize buttonSize = TTextView_Button_Size;
CGFloat bottomMargin = (TTextView_Height - buttonSize.height) * 0.5;
CGFloat originY = frame.size.height - buttonSize.height - bottomMargin;
CGRect faceFrame = _faceButton.frame;
faceFrame.origin.y = originY;
_faceButton.frame = faceFrame;
CGRect moreFrame = _moreButton.frame;
moreFrame.origin.y = originY;
_moreButton.frame = moreFrame;
CGRect voiceFrame = _micButton.frame;
voiceFrame.origin.y = originY;
_micButton.frame = voiceFrame;
[_keyboardButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(_faceButton);
}];
if (_delegate && [_delegate respondsToSelector:@selector(inputBar:didChangeInputHeight:)]) {
[_delegate inputBar:self didChangeInputHeight:offset];
}
}
#pragma mark - Event response
- (void)onMicButtonClicked:(UIButton *)sender {
2025-09-29 17:21:49 +08:00
NSInteger isCanChat = [[NSUserDefaults standardUserDefaults] integerForKey:@"kIsCanChat"];
if (isCanChat != 1) {
[self alertChat];
return;
}
2025-08-14 10:07:49 +08:00
_recordButton.hidden = NO;
_inputTextView.hidden = YES;
_micButton.hidden = YES;
_keyboardButton.hidden = NO;
_faceButton.hidden = NO;
[_inputTextView resignFirstResponder];
[self layoutButton:TTextView_Height];
if (_delegate && [_delegate respondsToSelector:@selector(inputBarDidTouchMore:)]) {
[_delegate inputBarDidTouchVoice:self];
}
[_keyboardButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(_micButton);
}];
}
- (void)onKeyboardButtonClicked:(UIButton *)sender {
2025-09-29 17:21:49 +08:00
NSInteger isCanChat = [[NSUserDefaults standardUserDefaults] integerForKey:@"kIsCanChat"];
if (isCanChat != 1) {
[self alertChat];
return;
}
2025-08-14 10:07:49 +08:00
_micButton.hidden = NO;
_keyboardButton.hidden = YES;
_recordButton.hidden = YES;
_inputTextView.hidden = NO;
_faceButton.hidden = NO;
[self layoutButton:_inputTextView.frame.size.height + 2 * TTextView_Margin];
if (_delegate && [_delegate respondsToSelector:@selector(inputBarDidTouchKeyboard:)]) {
[_delegate inputBarDidTouchKeyboard:self];
}
}
- (void)onFaceEmojiButtonClicked:(UIButton *)sender {
2025-09-29 17:21:49 +08:00
NSInteger isCanChat = [[NSUserDefaults standardUserDefaults] integerForKey:@"kIsCanChat"];
if (isCanChat != 1) {
[self alertChat];
return;
}
2025-08-14 10:07:49 +08:00
_micButton.hidden = NO;
_faceButton.hidden = YES;
_keyboardButton.hidden = NO;
_recordButton.hidden = YES;
_inputTextView.hidden = NO;
if (_delegate && [_delegate respondsToSelector:@selector(inputBarDidTouchFace:)]) {
[_delegate inputBarDidTouchFace:self];
}
[_keyboardButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(_faceButton);
}];
}
- (void)onMoreButtonClicked:(UIButton *)sender {
2025-09-29 17:21:49 +08:00
NSInteger isCanChat = [[NSUserDefaults standardUserDefaults] integerForKey:@"kIsCanChat"];
if (isCanChat != 1) {
[self alertChat];
return;
}
2025-08-14 10:07:49 +08:00
if (_delegate && [_delegate respondsToSelector:@selector(inputBarDidTouchMore:)]) {
[_delegate inputBarDidTouchMore:self];
}
}
- (void)onRecordButtonTouchDown:(UIButton *)sender {
[self.recorder record];
}
- (void)onRecordButtonTouchUpInside:(UIButton *)sender {
self.recordButton.backgroundColor = [UIColor clearColor];
[self.recordButton setTitle:TIMCommonLocalizableString(TUIKitInputHoldToTalk) forState:UIControlStateNormal];
NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:self.recordStartTime];
@weakify(self);
if (interval < 1) {
[self.recordView setStatus:Record_Status_TooShort];
[self.recorder cancel];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@strongify(self);
[self.recordView removeFromSuperview];
self.recordView = nil;
});
} else if (interval > MIN(59, [TUIChatConfig defaultConfig].maxAudioRecordDuration)) {
[self.recordView setStatus:Record_Status_TooLong];
[self.recorder cancel];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@strongify(self);
[self.recordView removeFromSuperview];
self.recordView = nil;
});
} else {
/// TUICallKit may need some time to stop all services, so remove UI immediately then stop the recorder.
if (_recordView) {
[self.recordView removeFromSuperview];
self.recordView = nil;
}
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(main_queue, ^{
@strongify(self);
dispatch_async(main_queue, ^{
[self.recorder stop];
NSString *path = self.recorder.recordedFilePath;
if (path) {
if (self.delegate && [self.delegate respondsToSelector:@selector(inputBar:didSendVoice:)]) {
[self.delegate inputBar:self didSendVoice:path];
}
}
});
});
}
}
- (void)onRecordButtonTouchCancel:(UIButton *)sender {
[self.recordView removeFromSuperview];
self.recordView = nil;
self.recordButton.backgroundColor = [UIColor clearColor];
[self.recordButton setTitle:TIMCommonLocalizableString(TUIKitInputHoldToTalk) forState:UIControlStateNormal];
[self.recorder cancel];
}
- (void)onRecordButtonTouchDragExit:(UIButton *)sender {
[self.recordView setStatus:Record_Status_Cancel];
[_recordButton setTitle:TIMCommonLocalizableString(TUIKitInputReleaseToCancel) forState:UIControlStateNormal];
}
- (void)onRecordButtonTouchDragEnter:(UIButton *)sender {
[self.recordView setStatus:Record_Status_Recording];
[_recordButton setTitle:TIMCommonLocalizableString(TUIKitInputReleaseToSend) forState:UIControlStateNormal];
}
- (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
}
}
2025-09-29 17:21:49 +08:00
-(void)alertChat{
NSString* canChatMoney = [[NSUserDefaults standardUserDefaults] objectForKey:@"kIsCanChatMoney"];
UIWindow*keyWindow = nil;
NSArray *windows = [UIApplication sharedApplication].windows;
for (UIWindow *window in windows){
if(window.isKeyWindow){
keyWindow = window;
break;
}
}
UINavigationController*na = (UINavigationController*)keyWindow.rootViewController;
NSString *title = [NSString stringWithFormat:@"充值不足%@元,您无法发送消息",canChatMoney];
UIAlertController *al = [UIAlertController alertControllerWithTitle:title message:@"" preferredStyle:(UIAlertControllerStyleAlert)];
[al addAction:[UIAlertAction actionWithTitle:@"去充值" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
UIViewController *vc = [[NSClassFromString(@"QXRechargeViewcController") alloc] init];
UINavigationController *naV = [[UINavigationController alloc] initWithRootViewController:vc];
naV.modalPresentationStyle = UIModalPresentationFullScreen;
[na presentViewController:naV animated:YES completion:nil];
}]];
[al addAction:[UIAlertAction actionWithTitle:@"取消" style:(UIAlertActionStyleCancel) handler:^(UIAlertAction * _Nonnull action) {
}]];
[na presentViewController:al animated:YES completion:nil];
}
2025-08-14 10:07:49 +08:00
#pragma mark - Text input
#pragma mark-- UITextViewDelegate
- (void)textViewDidBeginEditing:(UITextView *)textView {
2025-09-23 09:13:16 +08:00
NSInteger isCanChat = [[NSUserDefaults standardUserDefaults] integerForKey:@"kIsCanChat"];
if (isCanChat != 1) {
[self.inputTextView resignFirstResponder];
2025-09-29 17:21:49 +08:00
[self alertChat];
2025-09-23 09:13:16 +08:00
return;
}
2025-08-14 10:07:49 +08:00
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:^{
[ws.inputTextView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(ws.micButton.mas_trailing).mas_offset(10);
make.trailing.mas_equalTo(ws.faceButton.mas_leading).mas_offset(-10);
make.height.mas_equalTo(newHeight);
make.centerY.mas_equalTo(self);
}];
[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 *)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;
NSString *localizableFaceName = emoji.name;
// Set tag and image
emojiTextAttachment.emojiTag = localizableFaceName;
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;
// In iOS 15.0 and later, you need set styles again as belows
_inputTextView.textColor = kTUIInputNormalTextColor;
_inputTextView.font = kTUIInputNoramlFont;
}
- (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 onKeyboardButtonClicked:self.keyboardButton];
}
- (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 draft
[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 updateViewsToRecordingStatus];
}
- (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)updateViewsToRecordingStatus {
[self.window addSubview:self.recordView];
[self.recordView mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.window);
make.width.height.mas_equalTo(self.window);
}];
self.recordStartTime = [NSDate date];
[self.recordView setStatus:Record_Status_Recording];
self.recordButton.backgroundColor = [UIColor lightGrayColor];
[self.recordButton setTitle:TIMCommonLocalizableString(TUIKitInputReleaseToSend) forState:UIControlStateNormal];
[self showHapticFeedback];
}
- (void)audioRecorder:(TUIAudioRecorder *)recorder didPowerChanged:(float)power {
if (!self.recordView.hidden) {
[self.recordView setPower:power];
}
}
- (void)audioRecorder:(TUIAudioRecorder *)recorder didRecordTimeChanged:(NSTimeInterval)time {
float uiMaxDuration = MIN(59, [TUIChatConfig defaultConfig].maxAudioRecordDuration);
float realMaxDuration = uiMaxDuration + 0.7;
NSInteger seconds = uiMaxDuration - time;
self.recordView.timeLabel.text = [[NSString alloc] initWithFormat:@"%ld\"", (long)seconds + 1];
if (time >= (uiMaxDuration - 4) && time <= uiMaxDuration) {
NSInteger seconds = uiMaxDuration - time;
/**
* The long type is cast here to eliminate compiler warnings.
* Here +1 is to round up and optimize the time logic.
*/
self.recordView.title.text = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitInputWillFinishRecordInSeconds), (long)seconds + 1];
} else if (time > realMaxDuration) {
[self.recorder stop];
NSString *path = self.recorder.recordedFilePath;
[self.recordView setStatus:Record_Status_TooLong];
@weakify(self);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@strongify(self);
[self.recordView removeFromSuperview];
self.recordView = nil;
});
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;
}
- (TUIRecordView *)recordView {
if (!_recordView) {
_recordView = [[TUIRecordView alloc] init];
_recordView.frame = self.frame;
}
return _recordView;
}
@end