Files
featherVoice/TUIKit/TUIChat/UI_Minimalist/Input/TUIInputBar_Minimalist.m
2025-08-08 10:49:36 +08:00

668 lines
29 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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