// // TUIInputBar_Minimalist.m // UIKit // // Created by kennethmiao on 2018/9/18. // Copyright © 2018 Tencent. All rights reserved. // #import "TUIInputBar_Minimalist.h" #import #import #import #import #import #import #import #import #import "ReactiveObjC/ReactiveObjC.h" #import "TUIAudioRecorder.h" #import "TUIChatConfig.h" @interface TUIInputBar_Minimalist () @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