增加换肤功能
This commit is contained in:
193
TUIKit/TUIChat/UI_Minimalist/Input/TUIInputBar_Minimalist.h
Normal file
193
TUIKit/TUIChat/UI_Minimalist/Input/TUIInputBar_Minimalist.h
Normal 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
|
||||
667
TUIKit/TUIChat/UI_Minimalist/Input/TUIInputBar_Minimalist.m
Normal file
667
TUIKit/TUIChat/UI_Minimalist/Input/TUIInputBar_Minimalist.m
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
62
TUIKit/TUIChat/UI_Minimalist/Input/TUIMenuView_Minimalist.h
Normal file
62
TUIKit/TUIChat/UI_Minimalist/Input/TUIMenuView_Minimalist.h
Normal 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
|
||||
122
TUIKit/TUIChat/UI_Minimalist/Input/TUIMenuView_Minimalist.m
Normal file
122
TUIKit/TUIChat/UI_Minimalist/Input/TUIMenuView_Minimalist.m
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user