1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-02 09:27:55 +01:00
Telegram/Telegraph/TGModernConversationInputTextPanel.m
2016-02-25 01:03:51 +01:00

2482 lines
98 KiB
Objective-C

/*
* This is the source code of Telegram for iOS v. 1.1
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Peter Iakovlev, 2013.
*/
#import "TGModernConversationInputTextPanel.h"
#import "TGStickerKeyboardView.h"
#import "TGStickersSignals.h"
#import <MTProtoKit/MTTime.h>
#import "TGHacks.h"
#import "TGFont.h"
#import "TGImageUtils.h"
#import "TGStringUtils.h"
#import "TGTimerTarget.h"
#import "TGViewController.h"
#import "HPGrowingTextView.h"
#import "HPTextViewInternal.h"
#import "TGModernButton.h"
#import "TGModernConversationInputMicButton.h"
#import "TGModernConversationAssociatedInputPanel.h"
#import "TGStickerAssociatedInputPanel.h"
#import "TGCommandKeyboardView.h"
#import "TGModenConcersationReplyAssociatedPanel.h"
#import "TGModernConversationCommandsAssociatedPanel.h"
#import "TGAppDelegate.h"
#import "TGMessage.h"
static void setViewFrame(UIView *view, CGRect frame)
{
CGAffineTransform transform = view.transform;
view.transform = CGAffineTransformIdentity;
if (!CGRectEqualToRect(view.frame, frame))
view.frame = frame;
view.transform = transform;
}
static CGRect viewFrame(UIView *view)
{
CGAffineTransform transform = view.transform;
view.transform = CGAffineTransformIdentity;
CGRect result = view.frame;
view.transform = transform;
return result;
}
@implementation TGMessageEditingContext
- (instancetype)initWithText:(NSString *)text isCaption:(bool)isCaption messageId:(int32_t)messageId {
self = [super init];
if (self != nil) {
_text = text;
_isCaption = isCaption;
_messageId = messageId;
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
return [self initWithText:[aDecoder decodeObjectForKey:@"text"] isCaption:[aDecoder decodeBoolForKey:@"isCaption"] messageId:[aDecoder decodeInt32ForKey:@"messageId"]];
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_text forKey:@"text"];
[aCoder encodeBool:_isCaption forKey:@"isCaption"];
[aCoder encodeInt32:_messageId forKey:@"messageId"];
}
- (BOOL)isEqual:(id)object {
if ([object isKindOfClass:[TGMessageEditingContext class]] && TGStringCompare(((TGMessageEditingContext *)object)->_text, _text) && ((TGMessageEditingContext *)object)->_isCaption == _isCaption && ((TGMessageEditingContext *)object)->_messageId == _messageId) {
return true;
}
return false;
}
@end
@interface TGModernConversationInputTextPanel () <HPGrowingTextViewDelegate, TGModernConversationInputMicButtonDelegate>
{
id<SDisposable> _stickerPacksDisposable;
CALayer *_stripeLayer;
UIView *_backgroundView;
CGFloat _sendButtonWidth;
CGSize _messageAreaSize;
CGFloat _keyboardHeight;
#if TG_ENABLE_AUDIO_NOTES
TGModernConversationInputMicButton *_micButton;
UIImageView *_micButtonIconView;
#endif
UIView *_audioRecordingContainer;
NSUInteger _audioRecordingDurationSeconds;
NSUInteger _audioRecordingDurationMilliseconds;
NSTimer *_audioRecordingTimer;
UIImageView *_recordIndicatorView;
UILabel *_recordDurationLabel;
UIImageView *_slideToCancelArrow;
UILabel *_slideToCancelLabel;
CFAbsoluteTime _recordingInterfaceShowTime;
TGModernConversationAssociatedInputPanel *_associatedPanel;
TGModernConversationAssociatedInputPanel *_firstExtendedPanel;
TGModernConversationAssociatedInputPanel *_secondExtendedPanel;
TGModernConversationAssociatedInputPanel *_currentExtendedPanel;
TGModernButton *_keyboardModeButton;
TGModernButton *_stickerModeButton;
TGModernButton *_commandModeButton;
TGModernButton *_slashModeButton;
TGModernButton *_broadcastButton;
TGModernButton *_progressButton;
UIActivityIndicatorView *_progressButtonIndicator;
NSArray *_modeButtons;
TGStickerKeyboardView *_stickerKeyboardView;
bool _shouldShowKeyboardAutomatically;
UIView *_overlayDisabledView;
CGSize _parentSize;
bool _removeGifTabTextOnDeactivation;
UILabel *_contextPlaceholderLabel;
bool _recording;
TGMessageEditingContext *_messageEditingContext;
bool _messageEditingContextInvalidated;
}
@end
@implementation TGModernConversationInputTextPanel
- (instancetype)initWithFrame:(CGRect)frame accessoryView:(UIView *)panelAccessoryView
{
self = [super initWithFrame:frame];
if (self)
{
_sendButtonWidth = MIN(150.0f, [TGLocalized(@"Conversation.Send") sizeWithFont:TGMediumSystemFontOfSize(17)].width + 8.0f);
_panelAccessoryView = panelAccessoryView;
_backgroundView = [[UIView alloc] init];
_backgroundView.backgroundColor = UIColorRGBA(0xfafafa, 0.98f);
[self addSubview:_backgroundView];
_stripeLayer = [[CALayer alloc] init];
_stripeLayer.backgroundColor = UIColorRGBA(0xb3aab2, 0.4f).CGColor;
[self.layer addSublayer:_stripeLayer];
static UIImage *fieldBackgroundImage = nil;
static UIImage *attachImage = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
fieldBackgroundImage = [[UIImage imageNamed:@"ModernConversationInput.png"] stretchableImageWithLeftCapWidth:8 topCapHeight:8];
attachImage = [UIImage imageNamed:@"ModernConversationAttach.png"];
});
_fieldBackground = [[UIImageView alloc] initWithImage:fieldBackgroundImage];
setViewFrame(_fieldBackground, CGRectMake(41, 9, self.frame.size.width - 41 - _sendButtonWidth - 1, 28));
_fieldBackground.userInteractionEnabled = true;
[_fieldBackground addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(fieldBackgroundTapGesture:)]];
[self addSubview:_fieldBackground];
CGRect fieldBackgroundFrame = viewFrame(_fieldBackground);
setViewFrame(_panelAccessoryView, CGRectMake(CGRectGetMaxX(fieldBackgroundFrame) - _panelAccessoryView.frame.size.width, fieldBackgroundFrame.origin.y - 1.0f, _panelAccessoryView.frame.size.width, _panelAccessoryView.frame.size.height));
[self addSubview:_panelAccessoryView];
CGPoint placeholderOffset = [self inputFieldPlaceholderOffset];
_inputFieldPlaceholder = [[UIImageView alloc] init];
[self _updatePlaceholderImage];
setViewFrame(_inputFieldPlaceholder, CGRectOffset(_inputFieldPlaceholder.bounds, placeholderOffset.x, placeholderOffset.y));
[_fieldBackground addSubview:_inputFieldPlaceholder];
UIImage *stickerModeImage = [UIImage imageNamed:@"ConversationInputFieldStickerIcon.png"];
_stickerModeButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, stickerModeImage.size.width, 28.0f)];
[_stickerModeButton setImage:stickerModeImage forState:UIControlStateNormal];
[_stickerModeButton addTarget:self action:@selector(stickerModeButtonPressed) forControlEvents:UIControlEventTouchUpInside];
_stickerModeButton.adjustsImageWhenHighlighted = false;
UIImage *commandModeImage = [UIImage imageNamed:@"ConversationInputFieldActionsIcon.png"];
_commandModeButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, commandModeImage.size.width, 28.0f)];
[_commandModeButton setImage:commandModeImage forState:UIControlStateNormal];
[_commandModeButton addTarget:self action:@selector(commandModeButtonPressed) forControlEvents:UIControlEventTouchUpInside];
_commandModeButton.adjustsImageWhenHighlighted = false;
UIImage *slashModeImage = [UIImage imageNamed:@"ConversationInputFieldCommandIcon.png"];
_slashModeButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, slashModeImage.size.width, 28.0f)];
[_slashModeButton setImage:slashModeImage forState:UIControlStateNormal];
[_slashModeButton addTarget:self action:@selector(slashModeButtonPressed) forControlEvents:UIControlEventTouchUpInside];
_slashModeButton.adjustsImageWhenHighlighted = false;
UIImage *broadcastImage = _isBroadcasting ? [UIImage imageNamed:@"ConversationInputFieldBroadcastIconActive.png"] : [UIImage imageNamed:@"ConversationInputFieldBroadcastIconInactive.png"];
_broadcastButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, broadcastImage.size.width, 28.0f)];
[_broadcastButton setContentEdgeInsets:UIEdgeInsetsMake(0.0f, -2.0f, 0.0f, -2.0f)];
[_broadcastButton setImage:broadcastImage forState:UIControlStateNormal];
[_broadcastButton addTarget:self action:@selector(broadcastButtonPressed) forControlEvents:UIControlEventTouchUpInside];
_broadcastButton.adjustsImageWhenHighlighted = false;
_progressButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 28.0f, 28.0f)];
_progressButton.userInteractionEnabled = false;
_progressButtonIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:4];
_progressButtonIndicator.alpha = 0.5f;
[_progressButton addSubview:_progressButtonIndicator];
CGPoint progressCenter = _progressButton.center;
_progressButtonIndicator.center = CGPointMake(progressCenter.x + 8.0f, progressCenter.y + 0.5f);
UIImage *keyboardModeImage = [UIImage imageNamed:@"ConversationInputFieldKeyboardIcon.png"];
_keyboardModeButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, keyboardModeImage.size.width, 28.0f)];
[_keyboardModeButton setImage:keyboardModeImage forState:UIControlStateNormal];
[_keyboardModeButton addTarget:self action:@selector(keyboardModeButtonPressed) forControlEvents:UIControlEventTouchUpInside];
_keyboardModeButton.adjustsImageWhenHighlighted = false;
TGModernButton *sendButton = [[TGModernButton alloc] initWithFrame:CGRectZero];
sendButton.modernHighlight = true;
_sendButton = sendButton;
_sendButton.exclusiveTouch = true;
[_sendButton setTitle:TGLocalized(@"Conversation.Send") forState:UIControlStateNormal];
[_sendButton setTitleColor:TGAccentColor() forState:UIControlStateNormal];
[_sendButton setTitleColor:UIColorRGB(0x8e8e93) forState:UIControlStateDisabled];
_sendButton.titleLabel.font = [self sendButtonFont];
_sendButton.enabled = false;
[_sendButton addTarget:self action:@selector(sendButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_sendButton];
_attachButton = [[TGModernButton alloc] initWithFrame:CGRectMake(9, 11, attachImage.size.width, attachImage.size.height)];
_attachButton.exclusiveTouch = true;
[_attachButton setImage:attachImage forState:UIControlStateNormal];
[_attachButton addTarget:self action:@selector(attachButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_attachButton];
_micButton = [[TGModernConversationInputMicButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, _sendButtonWidth, 0.0f)];
_micButton.delegate = self;
_micButtonIconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"ModernConversationMicButton.png"]];
_micButton.iconView = _micButtonIconView;
[_micButton addSubview:_micButtonIconView];
[self addSubview:_micButton];
[self updateSendButtonVisibility];
[self updateModeButtonVisibility];
__weak TGModernConversationInputTextPanel *weakSelf = self;
_stickerPacksDisposable = [[[[TGStickersSignals stickerPacks] startOn:[SQueue concurrentDefaultQueue]] deliverOn:[SQueue mainQueue]] startWithNext:^(__unused NSDictionary *stickerPacks)
{
__strong TGModernConversationInputTextPanel *strongSelf = weakSelf;
if (strongSelf != nil)
{
[strongSelf updateModeButtonVisibility];
}
}];
}
return self;
}
- (void)dealloc
{
[_stickerPacksDisposable dispose];
[self stopAudioRecordingTimer];
}
- (void)_updatePlaceholderImage {
static int localizationVersion = 0;
static UIImage *placeholderImage = nil;
static UIImage *placeholderBroadcastImage = nil;
static UIImage *placeholderSilentBroadcastImage = nil;
static UIImage *placeholderDisabledImage = nil;
static UIImage *placeholderCommentImage = nil;
if (placeholderImage == nil || localizationVersion != TGLocalizedStaticVersion)
{
{
NSString *placeholderText = TGLocalized(@"Conversation.InputTextPlaceholder");
UIFont *placeholderFont = TGSystemFontOfSize(16);
CGSize placeholderSize = [placeholderText sizeWithFont:placeholderFont];
placeholderSize.width += 2.0f;
placeholderSize.height += 2.0f;
UIGraphicsBeginImageContextWithOptions(placeholderSize, false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGB(0xbebec0).CGColor);
[placeholderText drawAtPoint:CGPointMake(1.0f, 1.0f) withFont:placeholderFont];
placeholderImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
{
NSString *placeholderText = TGLocalized(@"Conversation.InputTextCommentPlaceholder");
UIFont *placeholderFont = TGSystemFontOfSize(16);
CGSize placeholderSize = [placeholderText sizeWithFont:placeholderFont];
placeholderSize.width += 2.0f;
placeholderSize.height += 2.0f;
UIGraphicsBeginImageContextWithOptions(placeholderSize, false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGB(0xbebec0).CGColor);
[placeholderText drawAtPoint:CGPointMake(1.0f, 1.0f) withFont:placeholderFont];
placeholderCommentImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
{
NSString *placeholderBroadcastText = TGLocalized(@"Conversation.InputTextBroadcastPlaceholder");
UIFont *placeholderFont = TGSystemFontOfSize(16);
CGSize placeholderSize = [placeholderBroadcastText sizeWithFont:placeholderFont];
placeholderSize.width += 2.0f;
placeholderSize.height += 2.0f;
UIGraphicsBeginImageContextWithOptions(placeholderSize, false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGB(0xbebec0).CGColor);
[placeholderBroadcastText drawAtPoint:CGPointMake(1.0f, 1.0f) withFont:placeholderFont];
placeholderBroadcastImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
{
NSString *placeholderSilentBroadcastText = TGLocalized(@"Conversation.InputTextSilentBroadcastPlaceholder");
UIFont *placeholderFont = TGSystemFontOfSize(16);
CGSize placeholderSize = [placeholderSilentBroadcastText sizeWithFont:placeholderFont];
placeholderSize.width += 2.0f;
placeholderSize.height += 2.0f;
UIGraphicsBeginImageContextWithOptions(placeholderSize, false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGB(0xbebec0).CGColor);
[placeholderSilentBroadcastText drawAtPoint:CGPointMake(1.0f, 1.0f) withFont:placeholderFont];
placeholderSilentBroadcastImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
{
NSString *placeholderDisabledText = TGLocalized(@"Conversation.InputTextDisabledPlaceholder");
UIFont *placeholderFont = TGSystemFontOfSize(16);
CGSize placeholderSize = [placeholderDisabledText sizeWithFont:placeholderFont];
placeholderSize.width += 2.0f;
placeholderSize.height += 2.0f;
UIGraphicsBeginImageContextWithOptions(placeholderSize, false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGB(0xbebec0).CGColor);
[placeholderDisabledText drawAtPoint:CGPointMake(1.0f, 1.0f) withFont:placeholderFont];
placeholderDisabledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
localizationVersion = TGLocalizedStaticVersion;
}
if (_inputDisabled) {
_inputFieldPlaceholder.image = placeholderDisabledImage;
} else if (_messageEditingContext != nil) {
_inputFieldPlaceholder.image = placeholderImage;
} else if (_canBroadcast || _isAlwaysBroadcasting) {
_inputFieldPlaceholder.image = _isBroadcasting ? placeholderBroadcastImage : placeholderSilentBroadcastImage;
} else if (_isChannel) {
_inputFieldPlaceholder.image = placeholderCommentImage;
} else {
_inputFieldPlaceholder.image = placeholderImage;
}
[_inputFieldPlaceholder sizeToFit];
}
- (HPGrowingTextView *)maybeInputField
{
return _inputField;
}
- (HPGrowingTextView *)inputField
{
if (_inputField == nil)
{
CGRect inputFieldClippingFrame = _fieldBackground.frame;
inputFieldClippingFrame.size.width -= _panelAccessoryView.frame.size.width;
_inputFieldClippingContainer = [[UIView alloc] initWithFrame:inputFieldClippingFrame];
_inputFieldClippingContainer.clipsToBounds = true;
[self addSubview:_inputFieldClippingContainer];
UIEdgeInsets inputFieldInternalEdgeInsets = [self inputFieldInternalEdgeInsets];
_inputField = [[HPGrowingTextView alloc] initWithFrame:CGRectMake(inputFieldInternalEdgeInsets.left, inputFieldInternalEdgeInsets.top, _inputFieldClippingContainer.frame.size.width - inputFieldInternalEdgeInsets.left, _inputFieldClippingContainer.frame.size.height)];
_inputField.placeholderView = _inputFieldPlaceholder;
_inputField.font = TGSystemFontOfSize(16);
_inputField.clipsToBounds = true;
_inputField.backgroundColor = nil;
_inputField.opaque = false;
_inputField.internalTextView.backgroundColor = nil;
_inputField.internalTextView.opaque = false;
_inputField.internalTextView.contentMode = UIViewContentModeLeft;
_inputField.maxNumberOfLines = [self _maxNumberOfLinesForSize:_parentSize];
_inputField.delegate = self;
_inputField.internalTextView.scrollIndicatorInsets = UIEdgeInsetsMake(-inputFieldInternalEdgeInsets.top, 0, 5 - TGRetinaPixel, 0);
[_inputFieldClippingContainer addSubview:_inputField];
}
return _inputField;
}
- (BOOL)growingTextViewEnabled:(HPGrowingTextView *)__unused growingTextView
{
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(isInputPanelTextEnabled:)])
return [delegate isInputPanelTextEnabled:self];
return true;
}
- (void)growingTextViewDidBeginEditing:(HPGrowingTextView *)__unused growingTextView
{
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelFocused:)])
[delegate inputPanelFocused:self];
[self updateAssociatedPanelVisibility:true];
}
- (void)replaceMention:(NSString *)mention
{
[TGModernConversationInputTextPanel replaceMention:mention inputField:_inputField];
}
+ (void)replaceMention:(NSString *)mention inputField:(HPGrowingTextView *)inputField
{
NSString *replacementText = [mention stringByAppendingString:@" "];
NSString *text = inputField.text;
UITextRange *selRange = inputField.internalTextView.selectedTextRange;
UITextPosition *selStartPos = selRange.start;
NSInteger idx = [inputField.internalTextView offsetFromPosition:inputField.internalTextView.beginningOfDocument toPosition:selStartPos];
idx--;
NSRange candidateMentionRange = NSMakeRange(NSNotFound, 0);
if (idx >= 0 && idx < (int)text.length)
{
for (NSInteger i = idx; i >= 0; i--)
{
unichar c = [text characterAtIndex:i];
if (c == '@')
{
if (i == idx)
candidateMentionRange = NSMakeRange(i + 1, 0);
else
candidateMentionRange = NSMakeRange(i + 1, idx - i);
break;
}
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'))
break;
}
}
if (candidateMentionRange.location != NSNotFound)
{
text = [text stringByReplacingCharactersInRange:candidateMentionRange withString:replacementText];
[inputField setText:text];
UITextPosition *textPosition = [inputField.internalTextView positionFromPosition:inputField.internalTextView.beginningOfDocument offset:candidateMentionRange.location + replacementText.length];
[inputField.internalTextView setSelectedTextRange:[inputField.internalTextView textRangeFromPosition:textPosition toPosition:textPosition]];
}
}
- (void)replaceHashtag:(NSString *)hashtag
{
[TGModernConversationInputTextPanel replaceHashtag:hashtag inputField:_inputField];
}
+ (void)replaceHashtag:(NSString *)hashtag inputField:(HPGrowingTextView *)inputField
{
static NSCharacterSet *characterSet = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
characterSet = [NSCharacterSet alphanumericCharacterSet];
});
NSString *replacementText = [hashtag stringByAppendingString:@" "];
NSString *text = inputField.text;
UITextRange *selRange = inputField.internalTextView.selectedTextRange;
UITextPosition *selStartPos = selRange.start;
NSInteger idx = [inputField.internalTextView offsetFromPosition:inputField.internalTextView.beginningOfDocument toPosition:selStartPos];
idx--;
NSRange candidateHashtagRange = NSMakeRange(NSNotFound, 0);
if (idx >= 0 && idx < (int)text.length)
{
for (NSInteger i = idx; i >= 0; i--)
{
unichar c = [text characterAtIndex:i];
if (c == '#')
{
if (i == idx)
candidateHashtagRange = NSMakeRange(i + 1, 0);
else
candidateHashtagRange = NSMakeRange(i + 1, idx - i);
break;
}
if (c == ' ' || (![characterSet characterIsMember:c] && c != '_'))
break;
}
}
if (candidateHashtagRange.location != NSNotFound)
{
text = [text stringByReplacingCharactersInRange:candidateHashtagRange withString:replacementText];
[inputField setText:text];
UITextPosition *textPosition = [inputField.internalTextView positionFromPosition:inputField.internalTextView.beginningOfDocument offset:candidateHashtagRange.location + replacementText.length];
[inputField.internalTextView setSelectedTextRange:[inputField.internalTextView textRangeFromPosition:textPosition toPosition:textPosition]];
}
}
- (NSString *)_inputTextForText:(NSString *)text byAddingString:(NSString *)string
{
NSString *newText = nil;
if (text.length > 0 && [[text substringFromIndex:text.length - 1] hasNonWhitespaceCharacters])
newText = [NSString stringWithFormat:@"%@ %@", text, string];
else
newText = [NSString stringWithFormat:@"%@%@", text, string];
return newText;
}
- (void)startMention
{
self.inputField.text = [self _inputTextForText:self.inputField.text byAddingString:@"@"];
self.inputField.internalTextView.enableFirstResponder = true;
[self.inputField becomeFirstResponder];
}
- (void)startHashtag
{
self.inputField.text = [self _inputTextForText:self.inputField.text byAddingString:@"#"];
self.inputField.internalTextView.enableFirstResponder = true;
[self.inputField becomeFirstResponder];
}
- (void)startCommand
{
if (self.inputField.text.length > 0 && [self.inputField.text hasNonWhitespaceCharacters])
return;
self.inputField.text = @"/";
self.inputField.internalTextView.enableFirstResponder = true;
[self.inputField becomeFirstResponder];
}
+ (NSString *)linkCandidateInText:(NSString *)text
{
NSString *lowercaseText = [text lowercaseString];
if ([lowercaseText rangeOfString:@"http://"].location == NSNotFound && [lowercaseText rangeOfString:@"https://"].location == NSNotFound)
{
return nil;
}
static NSDataDetector *dataDetector = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
dataDetector = [NSDataDetector dataDetectorWithTypes:(int)(NSTextCheckingTypeLink) error:NULL];
});
__block NSString *linkCandidate = nil;
[[dataDetector matchesInString:text options:0 range:NSMakeRange(0, text.length)] enumerateObjectsUsingBlock:^(NSTextCheckingResult *result, __unused NSUInteger idx, BOOL *stop)
{
NSString *lowercaseScheme = [[result URL].scheme lowercaseString];
if ([lowercaseScheme isEqualToString:@"http"] || [lowercaseScheme isEqualToString:@"https"])
{
linkCandidate = [[result URL] absoluteString];
if (stop)
*stop = true;
}
}];
return linkCandidate;
}
- (void)growingTextViewDidChange:(HPGrowingTextView *)growingTextView afterSetText:(bool)afterSetText afterPastingText:(bool)afterPastingText
{
if (!afterSetText) {
_removeGifTabTextOnDeactivation = false;
}
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
int textLength = (int)growingTextView.text.length;
NSString *text = growingTextView.text;
bool hasNonWhitespace = [text hasNonWhitespaceCharacters];
UITextRange *selRange = _inputField.internalTextView.selectedTextRange;
UITextPosition *selStartPos = selRange.start;
NSInteger idx = [_inputField.internalTextView offsetFromPosition:_inputField.internalTextView.beginningOfDocument toPosition:selStartPos];
idx--;
NSString *candidateMention = nil;
bool candidateMentionStartOfLine = false;
NSString *candidateMentionText = nil;
NSString *candidateHashtag = nil;
NSString *candidateCommand = nil;
static NSCharacterSet *characterSet = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
characterSet = [NSCharacterSet alphanumericCharacterSet];
});
if (idx >= 0 && idx < textLength)
{
for (NSInteger i = idx; i >= 0; i--)
{
unichar c = [text characterAtIndex:i];
unichar previousC = 0;
if (i > 0)
previousC = [text characterAtIndex:i - 1];
if (c == '@' && (previousC == 0 || ![characterSet characterIsMember:previousC]))
{
if (i == idx) {
candidateMention = @"";
candidateMentionStartOfLine = i == 0;
}
else
{
@try {
candidateMention = [text substringWithRange:NSMakeRange(i + 1, idx - i)];
candidateMentionStartOfLine = i == 0;
} @catch(NSException *e) { }
}
break;
}
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'))
break;
}
}
if (candidateMention == nil)
{
if (textLength != 0 && idx >= 0 && idx < textLength) {
unichar c = [text characterAtIndex:0];
if (c == '@') {
for (NSInteger i = 1; i <= idx; i++) {
c = [text characterAtIndex:i];
if (![characterSet characterIsMember:c] && c != '_') {
if (c == ' ') {
@try {
NSInteger limit = textLength;
for (NSInteger j = i + 1; j < textLength; j++) {
unichar c = [text characterAtIndex:j];
if (c == '\n') {
limit = j;
break;
}
}
candidateMention = [text substringWithRange:NSMakeRange(1, i - 1)];
candidateMentionText = [text substringWithRange:NSMakeRange(i + 1, limit - i - 1)];
} @catch(NSException *e) {
}
}
break;
}
}
}
}
if (candidateMention == nil || candidateMentionText == nil) {
if (idx >= 0 && idx < textLength)
{
for (NSInteger i = idx; i >= 0; i--)
{
unichar c = [text characterAtIndex:i];
if (c == '#')
{
if (i == idx)
candidateHashtag = @"";
else
{
@try {
candidateHashtag = [text substringWithRange:NSMakeRange(i + 1, idx - i)];
} @catch(NSException *e) { }
}
break;
}
if (c == ' ' || (![characterSet characterIsMember:c] && c != '_'))
break;
}
}
if (candidateHashtag == nil)
{
if (idx >= 0 && idx < textLength)
{
for (NSInteger i = idx; i >= 0; i--)
{
unichar c = [text characterAtIndex:i];
if (c == '/' && i == 0)
{
if (i == idx)
candidateCommand = @"";
else
{
@try {
candidateCommand = [text substringWithRange:NSMakeRange(i + 1, idx - i)];
} @catch(NSException *e) { }
}
break;
}
if (c == ' ' || (![characterSet characterIsMember:c] && c != '_'))
break;
}
}
}
}
}
if ([delegate respondsToSelector:@selector(inputPanelMentionEntered:mention:startOfLine:)])
[delegate inputPanelMentionEntered:self mention:candidateMentionText == nil ? candidateMention : nil startOfLine:candidateMentionStartOfLine];
if ([delegate respondsToSelector:@selector(inputPanelMentionTextEntered:mention:text:)])
[delegate inputPanelMentionTextEntered:self mention:candidateMention text:candidateMentionText];
if ([delegate respondsToSelector:@selector(inputPanelHashtagEntered:hashtag:)])
[delegate inputPanelHashtagEntered:self hashtag:candidateHashtag];
if ([delegate respondsToSelector:@selector(inputPanelCommandEntered:command:)])
[delegate inputPanelCommandEntered:self command:candidateCommand];
NSString *linkCandidate = [TGModernConversationInputTextPanel linkCandidateInText:text];
if ([delegate respondsToSelector:@selector(inputPanelLinkParsed:link:probablyComplete:)])
[delegate inputPanelLinkParsed:self link:linkCandidate probablyComplete:afterPastingText];
bool sendButtonEnabled = hasNonWhitespace;
if ([delegate respondsToSelector:@selector(inputPanelSendShouldBeAlwaysEnabled:)])
{
if ([delegate inputPanelSendShouldBeAlwaysEnabled:self])
sendButtonEnabled = true;
}
if (_sendButton.enabled != sendButtonEnabled)
_sendButton.enabled = sendButtonEnabled;
if ([delegate respondsToSelector:@selector(inputPanelTextChanged:text:)])
[delegate inputPanelTextChanged:self text:text];
[self updateSendButtonVisibility];
[self updateModeButtonVisibility];
if (!afterSetText)
{
if (hasNonWhitespace)
{
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputTextPanelHasIndicatedTypingActivity:)])
[delegate inputTextPanelHasIndicatedTypingActivity:self];
}
}
if (!hasNonWhitespace)
{
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputTextPanelHasCancelledTypingActivity:)])
[delegate inputTextPanelHasCancelledTypingActivity:self];
}
if (_contextPlaceholderLabel.superview != nil) {
[self setNeedsLayout];
}
}
- (void)updateModeButtonVisibility
{
NSMutableArray *commands = [[NSMutableArray alloc] init];
if (_messageEditingContext == nil) {
if (self.maybeInputField.internalTextView.inputView != nil) {
[commands addObject:_keyboardModeButton];
if (_canBroadcast && !_isAlwaysBroadcasting) {
[commands addObject:_broadcastButton];
}
} else
{
if (_inputField.text.length == 0)
{
if (!(TGAppDelegateInstance.alwaysShowStickersMode != 2))
[commands addObject:_stickerModeButton];
if ([self currentReplyMarkup] != nil)
[commands addObject:_commandModeButton];
else if (_hasBots)
[commands addObject:_slashModeButton];
}
if (_canBroadcast && !_isAlwaysBroadcasting) {
[commands addObject:_broadcastButton];
}
if (_displayProgress) {
[commands addObject:_progressButton];
}
}
}
[self setModeButtons:commands];
}
- (void)updateSendButtonVisibility
{
bool hidden = _inputField == nil || _inputField.text.length == 0;
if (!hidden)
{
NSString *text = _inputField.text;
NSUInteger length = text.length;
bool foundNonWhitespace = false;
for (NSUInteger i = 0; i < length; i++)
{
unichar c = [text characterAtIndex:i];
if (c != ' ')
{
foundNonWhitespace = true;
break;
}
}
if (!foundNonWhitespace)
hidden = true;
}
if (_messageEditingContext != nil) {
if (_messageEditingContext.isCaption) {
_sendButton.enabled = true;
} else {
_sendButton.enabled = !hidden;
}
hidden = false;
} else {
_sendButton.enabled = true;
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelSendShouldBeAlwaysEnabled:)])
{
if ([delegate inputPanelSendShouldBeAlwaysEnabled:self])
hidden = false;
}
}
_sendButton.hidden = hidden;
_micButton.hidden = !_sendButton.hidden;
}
- (void)fieldBackgroundTapGesture:(UITapGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateRecognized)
{
if (self.maybeInputField.isFirstResponder && self.maybeInputField.internalTextView.inputView != nil)
{
[self keyboardModeButtonPressed];
}
else
{
[self inputField].internalTextView.enableFirstResponder = true;
[[self inputField].internalTextView becomeFirstResponder];
}
}
}
- (void)sendButtonPressed
{
if (_inputField.internalTextView.isFirstResponder)
[TGHacks applyCurrentKeyboardAutocorrectionVariant];
NSMutableString *text = [[NSMutableString alloc] initWithString:_inputField.text == nil ? @"" : _inputField.text];
int textLength = (int)text.length;
for (int i = 0; i < textLength; i++)
{
unichar c = [text characterAtIndex:i];
if (c == ' ' || c == '\t' || c == '\n')
{
[text deleteCharactersInRange:NSMakeRange(i, 1)];
i--;
textLength--;
}
else
break;
}
for (int i = textLength - 1; i >= 0; i--)
{
unichar c = [text characterAtIndex:i];
if (c == ' ' || c == '\t' || c == '\n')
{
[text deleteCharactersInRange:NSMakeRange(i, 1)];
textLength--;
}
else
break;
}
bool enableSend = text.length != 0;
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelSendShouldBeAlwaysEnabled:)])
{
if ([delegate inputPanelSendShouldBeAlwaysEnabled:self])
enableSend = true;
}
if (_messageEditingContext != nil && _messageEditingContext.isCaption) {
enableSend = true;
}
if (enableSend)
{
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelRequestedSendMessage:text:)])
[delegate inputPanelRequestedSendMessage:self text:text];
}
}
- (void)attachButtonPressed
{
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelRequestedAttachmentsMenu:)])
[delegate inputPanelRequestedAttachmentsMenu:self];
}
- (void)micButtonInteractionBegan
{
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelAudioRecordingEnabled:)])
{
if (![delegate inputPanelAudioRecordingEnabled:self])
return;
}
if ([delegate respondsToSelector:@selector(inputPanelAudioRecordingStart:completion:)]) {
__weak TGModernConversationInputTextPanel *weakSelf = self;
[delegate inputPanelAudioRecordingStart:self completion:^{
TGModernConversationInputTextPanel *strongSelf = weakSelf;
if (strongSelf != nil) {
strongSelf->_recording = true;
[strongSelf setShowRecordingInterface:true velocity:0.0f];
}
}];
}
}
- (void)micButtonInteractionCancelled:(CGFloat)velocity
{
if (_recording) {
_recording = false;
[self setShowRecordingInterface:false velocity:velocity];
}
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelAudioRecordingCancel:)])
[delegate inputPanelAudioRecordingCancel:self];
}
- (void)micButtonInteractionCompleted:(CGFloat)velocity
{
if (_recording) {
_recording = false;
[self setShowRecordingInterface:false velocity:velocity];
}
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelAudioRecordingComplete:)])
[delegate inputPanelAudioRecordingComplete:self];
}
- (void)micButtonInteractionUpdate:(CGFloat)value
{
CGFloat offset = value * 100.0f;
offset = MAX(0.0f, offset - 5.0f);
if (value < 0.3f)
offset = value / 0.6f * offset;
else
offset -= 0.15f * 100.0f;
_slideToCancelArrow.transform = CGAffineTransformMakeTranslation(-offset, 0.0f);
CGAffineTransform labelTransform = CGAffineTransformIdentity;
labelTransform = CGAffineTransformTranslate(labelTransform, -offset, 0.0f);
_slideToCancelLabel.transform = labelTransform;
CGAffineTransform indicatorTransform = CGAffineTransformIdentity;
CGAffineTransform durationTransform = CGAffineTransformIdentity;
static CGFloat freeOffsetLimit = 35.0f;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
CGFloat labelWidth = [TGLocalized(@"Conversation.SlideToCancel") sizeWithFont:TGSystemFontOfSize(14.0f)].width;
CGFloat arrowOrigin = CGFloor((TGScreenSize().width - labelWidth) / 2.0f) - 9.0f - 6.0f;
CGFloat timerWidth = 82.0f;
freeOffsetLimit = MAX(0.0f, arrowOrigin - timerWidth);
});
if (offset > freeOffsetLimit)
{
indicatorTransform = CGAffineTransformMakeTranslation(freeOffsetLimit - offset, 0.0f);
durationTransform = CGAffineTransformMakeTranslation(freeOffsetLimit - offset, 0.0f);
}
if (!CGAffineTransformEqualToTransform(indicatorTransform, _recordIndicatorView.transform))
_recordIndicatorView.transform = indicatorTransform;
if (!CGAffineTransformEqualToTransform(durationTransform, _recordDurationLabel.transform))
_recordDurationLabel.transform = durationTransform;
}
- (int)_maxNumberOfLinesForSize:(CGSize)size
{
if (size.height <= 320.0f - FLT_EPSILON) {
return 3;
} else if (size.height <= 480.0f - FLT_EPSILON) {
return 5;
} else {
return 7;
}
}
- (void)adjustForSize:(CGSize)size keyboardHeight:(CGFloat)keyboardHeight duration:(NSTimeInterval)duration animationCurve:(int)animationCurve
{
CGSize previousSize = _parentSize;
_parentSize = size;
if (ABS(previousSize.width - size.width) > FLT_EPSILON) {
[self changeToSize:size keyboardHeight:keyboardHeight duration:0.0];
}
[self _adjustForSize:size keyboardHeight:keyboardHeight inputFieldHeight:_inputField == nil ? 36.0f : _inputField.frame.size.height duration:duration animationCurve:animationCurve];
}
- (CGFloat)baseHeight
{
static CGFloat value = 0.0f;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
value = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone ? 45.0f : 56.0f;
});
return value;
}
- (bool)shouldDisplayPanels
{
return [TGViewController hasLargeScreen] || _messageAreaSize.width <= _messageAreaSize.height || _keyboardHeight < FLT_EPSILON;
}
- (CGFloat)extendedPanelHeight
{
return [self shouldDisplayPanels] ? [_currentExtendedPanel preferredHeight] : 0.0f;
}
- (UIEdgeInsets)inputFieldInsets
{
static UIEdgeInsets insets;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
insets = UIEdgeInsetsMake(9.0f, 41.0f, 8.0f, 0.0f);
else
insets = UIEdgeInsetsMake(TGIsRetina() ? 12.0f : 12.0f, 58.0f, 12.0f, 21.0f);
});
return insets;
}
- (UIEdgeInsets)inputFieldInternalEdgeInsets
{
static UIEdgeInsets insets;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
insets = UIEdgeInsetsMake(-3 - TGRetinaPixel, 0.0f, 0.0f, 0.0f);
else
insets = UIEdgeInsetsMake(-1 - TGRetinaPixel, 4.0f, 0.0f, 0.0f);
});
return insets;
}
- (CGPoint)inputFieldPlaceholderOffset
{
static CGPoint offset;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
offset = CGPointMake(4.0f, 3.0f);
else
offset = CGPointMake(8.0f, 5.0f);
});
return offset;
}
- (CGFloat)heightForInputFieldHeight:(CGFloat)inputFieldHeight
{
if (TGIsPad())
inputFieldHeight += 4;
UIEdgeInsets inputFieldInsets = [self inputFieldInsets];
CGFloat height = MAX([self baseHeight], inputFieldHeight - 8 + inputFieldInsets.top + inputFieldInsets.bottom);
return height + [self extendedPanelHeight];
}
- (UIFont *)sendButtonFont
{
return TGMediumSystemFontOfSize(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone ? 17 : 18);
}
- (CGPoint)sendButtonOffset
{
static CGPoint offset;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
offset = CGPointZero;
else
offset = CGPointMake(-11.0f, -6.0f);
});
return offset;
}
- (void)_adjustForSize:(CGSize)size keyboardHeight:(CGFloat)keyboardHeight inputFieldHeight:(CGFloat)inputFieldHeight duration:(NSTimeInterval)duration animationCurve:(int)animationCurve
{
dispatch_block_t block = ^
{
CGSize messageAreaSize = size;
_messageAreaSize = messageAreaSize;
_keyboardHeight = keyboardHeight;
CGFloat inputContainerHeight = [self heightForInputFieldHeight:inputFieldHeight];
self.frame = CGRectMake(0, messageAreaSize.height - keyboardHeight - inputContainerHeight, messageAreaSize.width, inputContainerHeight);
[self layoutSubviews];
};
if (duration > DBL_EPSILON)
[UIView animateWithDuration:duration delay:0 options:animationCurve << 16 animations:block completion:nil];
else
block();
}
- (void)updateInputFieldLayout {
NSRange range = _inputField.internalTextView.selectedRange;
_inputField.delegate = nil;
UIEdgeInsets inputFieldInsets = [self inputFieldInsets];
UIEdgeInsets inputFieldInternalEdgeInsets = [self inputFieldInternalEdgeInsets];
CGRect inputFieldClippingFrame = CGRectMake(inputFieldInsets.left, inputFieldInsets.top, _parentSize.width - inputFieldInsets.left - inputFieldInsets.right - _sendButtonWidth - 1 - _panelAccessoryView.frame.size.width, 0.0f);
CGRect inputFieldFrame = CGRectMake(inputFieldInternalEdgeInsets.left, inputFieldInternalEdgeInsets.top, inputFieldClippingFrame.size.width - inputFieldInternalEdgeInsets.left, 0.0f);
setViewFrame(_inputField, inputFieldFrame);
[_inputField setMaxNumberOfLines:[self _maxNumberOfLinesForSize:_parentSize]];
[_inputField refreshHeight:false];
_inputField.internalTextView.selectedRange = range;
_inputField.delegate = self;
}
- (void)changeToSize:(CGSize)size keyboardHeight:(CGFloat)keyboardHeight duration:(NSTimeInterval)duration
{
_parentSize = size;
CGSize messageAreaSize = size;
_messageAreaSize = messageAreaSize;
_keyboardHeight = keyboardHeight;
UIView *inputFieldSnapshotView = nil;
if (duration > DBL_EPSILON)
{
inputFieldSnapshotView = [_inputField.internalTextView snapshotViewAfterScreenUpdates:false];
inputFieldSnapshotView.frame = CGRectOffset(_inputField.frame, _inputFieldClippingContainer.frame.origin.x, _inputFieldClippingContainer.frame.origin.y);
[self addSubview:inputFieldSnapshotView];
}
[UIView performWithoutAnimation:^
{
[self updateInputFieldLayout];
}];
CGFloat inputContainerHeight = [self heightForInputFieldHeight:_inputField.frame.size.height];
CGRect newInputContainerFrame = CGRectMake(0, messageAreaSize.height - keyboardHeight - inputContainerHeight, messageAreaSize.width, inputContainerHeight);
if (duration > DBL_EPSILON)
{
if (inputFieldSnapshotView != nil)
_inputField.alpha = 0.0f;
[UIView animateWithDuration:duration animations:^
{
self.frame = newInputContainerFrame;
[self layoutSubviews];
if (inputFieldSnapshotView != nil)
{
_inputField.alpha = 1.0f;
inputFieldSnapshotView.frame = CGRectOffset(_inputField.frame, _inputFieldClippingContainer.frame.origin.x, _inputFieldClippingContainer.frame.origin.y);
inputFieldSnapshotView.alpha = 0.0f;
}
} completion:^(__unused BOOL finished)
{
[inputFieldSnapshotView removeFromSuperview];
}];
}
else
{
self.frame = newInputContainerFrame;
}
}
- (void)growingTextView:(HPGrowingTextView *)__unused growingTextView willChangeHeight:(CGFloat)height duration:(NSTimeInterval)duration animationCurve:(int)animationCurve
{
UIEdgeInsets inputFieldInsets = [self inputFieldInsets];
CGFloat inputContainerHeight = MAX([self baseHeight], height - 8 + inputFieldInsets.top + inputFieldInsets.bottom) + [self extendedPanelHeight];
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelWillChangeHeight:height:duration:animationCurve:)])
{
[delegate inputPanelWillChangeHeight:self height:inputContainerHeight duration:duration animationCurve:animationCurve];
}
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGRect frame = self.frame;
if (_overlayDisabledView != nil) {
_overlayDisabledView.frame = self.bounds;
}
_stripeLayer.frame = CGRectMake(-3.0f, -TGRetinaPixel, frame.size.width + 6.0f, TGRetinaPixel);
_backgroundView.frame = CGRectMake(-3.0f, 0.0f, frame.size.width + 6.0f, frame.size.height);
bool displayPanels = [self shouldDisplayPanels];
if (_currentExtendedPanel != nil)
{
_currentExtendedPanel.frame = CGRectMake(0.0f, 0.0f, frame.size.width, displayPanels ? [_currentExtendedPanel preferredHeight] : 0.0f);
}
if (_associatedPanel != nil)
{
CGRect associatedPanelFrame = CGRectMake(0.0f, -[_associatedPanel preferredHeight] + _currentExtendedPanel.frame.size.height, frame.size.width, displayPanels ? [_associatedPanel preferredHeight] : 0.0f);
if (!CGRectEqualToRect(associatedPanelFrame, _associatedPanel.frame))
_associatedPanel.frame = associatedPanelFrame;
}
UIEdgeInsets inputFieldInsets = [self inputFieldInsets];
setViewFrame(_fieldBackground, CGRectMake(inputFieldInsets.left, inputFieldInsets.top + [self extendedPanelHeight], frame.size.width - inputFieldInsets.left - inputFieldInsets.right - _sendButtonWidth - 1, frame.size.height - inputFieldInsets.top - inputFieldInsets.bottom - [self extendedPanelHeight]));
if (_panelAccessoryView != nil)
{
setViewFrame(_panelAccessoryView, CGRectMake(CGRectGetMaxX(_fieldBackground.frame) - _panelAccessoryView.frame.size.width - 4.0f, _fieldBackground.frame.origin.y + 1.0f, _panelAccessoryView.frame.size.width, _panelAccessoryView.frame.size.height));
}
CGFloat accessoryViewInset = 0.0f;
if (_panelAccessoryView != nil)
accessoryViewInset = _panelAccessoryView.frame.size.width + 2.0f;
accessoryViewInset += 6.0f;
CGFloat modeButtonVerticalOffset = TGIsPad() ? 2.0f : 0.0f;
CGFloat modeButtonRightEdge = CGRectGetMaxX(_fieldBackground.frame) - accessoryViewInset - 4.0f;
CGFloat modeButtonSpacing = 9.0f;
for (UIButton *button in _modeButtons)
{
setViewFrame(button, CGRectMake(modeButtonRightEdge - button.frame.size.width, _fieldBackground.frame.origin.y + modeButtonVerticalOffset, button.frame.size.width, button.frame.size.height));
modeButtonRightEdge -= modeButtonSpacing + button.frame.size.width;
}
CGPoint sendButtonOffset = [self sendButtonOffset];
CGRect inputFieldClippingFrame = _fieldBackground.frame;
inputFieldClippingFrame.size.width -= _panelAccessoryView.frame.size.width;
setViewFrame(_inputFieldClippingContainer, inputFieldClippingFrame);
setViewFrame(_sendButton, CGRectMake(frame.size.width - _sendButtonWidth + sendButtonOffset.x * 2.0f, frame.size.height - [self baseHeight], _sendButtonWidth - sendButtonOffset.x * 2.0f, [self baseHeight] - 1.0f));
setViewFrame(_attachButton, CGRectMake(TGIsPad() ? (1.0f) : (-TGRetinaPixel), frame.size.height - [self baseHeight], inputFieldClippingFrame.origin.x - 1.0f, [self baseHeight] - (TGIsPad() ? 1 : 0)));
#if TG_ENABLE_AUDIO_NOTES
setViewFrame(_micButton, CGRectInset(_sendButton.frame, 0.0f, -2.0f));
setViewFrame(_micButtonIconView, CGRectMake(CGFloor((_micButton.frame.size.width - _micButtonIconView.frame.size.width) / 2.0f), CGFloor((_micButton.frame.size.height - _micButtonIconView.frame.size.height) / 2.0f) + 1.0f + TGRetinaPixel, _micButtonIconView.frame.size.width, _micButtonIconView.frame.size.height));
if (_slideToCancelLabel != nil)
{
CGRect slideToCancelLabelFrame = viewFrame(_slideToCancelLabel);
setViewFrame(_slideToCancelLabel, CGRectMake(CGFloor((self.frame.size.width - slideToCancelLabelFrame.size.width) / 2.0f), _currentExtendedPanel.frame.size.height + CGFloor((self.frame.size.height - _currentExtendedPanel.frame.size.height - slideToCancelLabelFrame.size.height) / 2.0f), slideToCancelLabelFrame.size.width, slideToCancelLabelFrame.size.height));
CGRect slideToCancelArrowFrame = viewFrame(_slideToCancelArrow);
setViewFrame(_slideToCancelArrow, CGRectMake(CGFloor((self.frame.size.width - slideToCancelLabelFrame.size.width) / 2.0f) - slideToCancelArrowFrame.size.width - 6.0f, _currentExtendedPanel.frame.size.height + CGFloor((self.frame.size.height - _currentExtendedPanel.frame.size.height - slideToCancelLabelFrame.size.height) / 2.0f) + 1.0f, slideToCancelArrowFrame.size.width, slideToCancelArrowFrame.size.height));
}
#endif
if (_contextPlaceholderLabel.superview != nil) {
NSString *text = _inputField.internalTextView.text;
CGFloat textWidth = CGCeil([text sizeWithFont:_inputField.internalTextView.font].width);
CGFloat maxWidth = modeButtonRightEdge - _fieldBackground.frame.origin.x - textWidth;
if (maxWidth > 24.0f) {
CGSize labelSize = [_contextPlaceholderLabel.text sizeWithFont:_contextPlaceholderLabel.font];
labelSize.width = CGCeil(labelSize.width);
labelSize.height = CGCeil(labelSize.height);
_contextPlaceholderLabel.frame = CGRectMake(textWidth + (TGIsPad() ? 8.0f : 4.0f), (TGIsPad() ? 6.0 : 4.0f) + TGRetinaPixel, MIN(labelSize.width, maxWidth), labelSize.height);
} else {
_contextPlaceholderLabel.frame = CGRectZero;
}
}
setViewFrame(_audioRecordingContainer, self.bounds);
}
- (void)addRecordingDotAnimation {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
animation.values = @[@1.0f, @1.0f, @0.0f];
animation.keyTimes = @[@.0, @0.4546, @0.9091, @1];
animation.duration = 0.5;
animation.duration = 0.5;
animation.autoreverses = true;
animation.repeatCount = INFINITY;
[_recordIndicatorView.layer addAnimation:animation forKey:@"opacity-dot"];
}
- (void)removeDotAnimation {
[_recordIndicatorView.layer removeAnimationForKey:@"opacity-dot"];
}
- (void)setShowRecordingInterface:(bool)show velocity:(CGFloat)velocity
{
#if TG_ENABLE_AUDIO_NOTES
if (show)
{
[_micButton animateIn];
_recordingInterfaceShowTime = CFAbsoluteTimeGetCurrent();
_micButtonIconView.image = [UIImage imageNamed:@"ModernConversationMicButton_Highlighted.png"];
if (_audioRecordingContainer == nil)
{
_audioRecordingContainer = [[UIView alloc] initWithFrame:self.bounds];
_audioRecordingContainer.clipsToBounds = true;
[self insertSubview:_audioRecordingContainer aboveSubview:_backgroundView];
}
if (_recordIndicatorView == nil)
{
static UIImage *indicatorImage = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
indicatorImage = TGCircleImage(9.0f, UIColorRGB(0xF33D2B));
});
_recordIndicatorView = [[UIImageView alloc] initWithImage:indicatorImage];
//_recordIndicatorView.alpha = 0.0f;
}
setViewFrame(_recordIndicatorView, CGRectMake(11.0f, _currentExtendedPanel.frame.size.height + CGFloor(([self baseHeight] - 9.0f) / 2.0f) + (TGIsPad() ? 1.0f : 0.0f), 9.0f, 9.0f));
_recordIndicatorView.transform = CGAffineTransformMakeTranslation(-80.0f, 0.0f);
if (_recordDurationLabel == nil)
{
_recordDurationLabel = [[UILabel alloc] init];
_recordDurationLabel.backgroundColor = [UIColor clearColor];
_recordDurationLabel.textColor = [UIColor blackColor];
_recordDurationLabel.font = TGSystemFontOfSize(15.0f);
_recordDurationLabel.text = @"0:00,00 ";
[_recordDurationLabel sizeToFit];
_recordDurationLabel.alpha = 0.0f;
_recordDurationLabel.layer.anchorPoint = CGPointMake((26.0f - _recordDurationLabel.frame.size.width) / (2 * 26.0f), 0.5f);
_recordDurationLabel.textAlignment = NSTextAlignmentLeft;
}
setViewFrame(_recordDurationLabel, CGRectMake(26.0f, _currentExtendedPanel.frame.size.height + CGFloor(([self baseHeight] - _recordDurationLabel.frame.size.height) / 2.0f), _recordDurationLabel.frame.size.width, _recordDurationLabel.frame.size.height));
_recordDurationLabel.transform = CGAffineTransformMakeTranslation(-80.0f, 0.0f);
if (_slideToCancelLabel == nil)
{
_slideToCancelLabel = [[UILabel alloc] init];
_slideToCancelLabel.backgroundColor = [UIColor clearColor];
_slideToCancelLabel.textColor = UIColorRGB(0xaaaab2);
_slideToCancelLabel.font = TGSystemFontOfSize(14.0f);
_slideToCancelLabel.text = TGLocalized(@"Conversation.SlideToCancel");
_slideToCancelLabel.clipsToBounds = false;
[_slideToCancelLabel sizeToFit];
setViewFrame(_slideToCancelLabel, CGRectMake(CGFloor((self.frame.size.width - _slideToCancelLabel.frame.size.width) / 2.0f), CGFloor((self.frame.size.height - _slideToCancelLabel.frame.size.height) / 2.0f), _slideToCancelLabel.frame.size.width, _slideToCancelLabel.frame.size.height));
_slideToCancelLabel.alpha = 0.0f;
_slideToCancelArrow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"ModernConversationAudioSlideToCancel.png"]];
CGRect slideToCancelArrowFrame = viewFrame(_slideToCancelArrow);
setViewFrame(_slideToCancelArrow, CGRectMake(CGFloor((self.frame.size.width - _slideToCancelLabel.frame.size.width) / 2.0f) - slideToCancelArrowFrame.size.width - 6.0f, CGFloor((self.frame.size.height - _slideToCancelLabel.frame.size.height) / 2.0f) + 1.0f, slideToCancelArrowFrame.size.width, slideToCancelArrowFrame.size.height));
_slideToCancelArrow.alpha = 0.0f;
[self addSubview:_slideToCancelArrow];
_slideToCancelArrow.transform = CGAffineTransformMakeTranslation(320.0f, 0.0f);
_slideToCancelLabel.transform = CGAffineTransformMakeTranslation(320.0f, 0.0f);
}
_recordDurationLabel.text = @"0:00,00";
if (_recordIndicatorView.superview == nil)
[_audioRecordingContainer addSubview:_recordIndicatorView];
[_recordIndicatorView.layer removeAllAnimations];
if (_recordDurationLabel.superview == nil)
[_audioRecordingContainer addSubview:_recordDurationLabel];
[_recordDurationLabel.layer removeAllAnimations];
if (_slideToCancelLabel.superview == nil)
[_audioRecordingContainer addSubview:_slideToCancelLabel];
_slideToCancelArrow.transform = CGAffineTransformMakeTranslation(300.0f, 0.0f);
_slideToCancelLabel.transform = CGAffineTransformMakeTranslation(300.0f, 0.0f);
[UIView animateWithDuration:0.26 delay:0.0 options:0 animations:^
{
_inputFieldClippingContainer.alpha = 0.0f;
_fieldBackground.alpha = 0.0f;
_fieldBackground.transform = CGAffineTransformMakeTranslation(-320.0f, 0.0f);
_panelAccessoryView.transform = CGAffineTransformMakeTranslation(-320.0f, 0.0f);
for (UIButton *button in _modeButtons)
{
button.transform = CGAffineTransformMakeTranslation(-320.0f, 0.0f);
}
_panelAccessoryView.alpha = 0.0f;
_inputFieldPlaceholder.alpha = 0.0f;
} completion:nil];
int animationCurveOption = iosMajorVersion() >= 7 ? (7 << 16) : 0;
[UIView animateWithDuration:0.15 animations:^
{
_attachButton.alpha = 0.0f;
_attachButton.transform = CGAffineTransformMakeTranslation(-320.0f, 0.0f);
}];
[UIView animateWithDuration:0.25 delay:0.06 options:animationCurveOption animations:^
{
//_recordIndicatorView.alpha = 1.0f;
_recordIndicatorView.transform = CGAffineTransformIdentity;
} completion:nil];
[UIView animateWithDuration:0.25 delay:0.0 options:animationCurveOption animations:^
{
_recordDurationLabel.alpha = 1.0f;
_recordDurationLabel.transform = CGAffineTransformIdentity;
} completion:nil];
[UIView animateWithDuration:0.18 delay:0.0 options:animationCurveOption animations:^
{
_slideToCancelArrow.alpha = 1.0f;
_slideToCancelArrow.transform = CGAffineTransformIdentity;
} completion:nil];
[UIView animateWithDuration:0.18 delay:0.04 options:animationCurveOption animations:^
{
_slideToCancelLabel.alpha = 1.0f;
_slideToCancelLabel.transform = CGAffineTransformIdentity;
} completion:nil];
[self addRecordingDotAnimation];
}
else
{
[_micButton animateOut];
[self removeDotAnimation];
NSTimeInterval durationFactor = MIN(0.4, MAX(1.0, velocity / 1000.0));
_micButtonIconView.image = [UIImage imageNamed:@"ModernConversationMicButton.png"];
int options = 0;
if (ABS(CFAbsoluteTimeGetCurrent() - _recordingInterfaceShowTime) < 0.2)
{
options = UIViewAnimationOptionBeginFromCurrentState;
}
else
{
_attachButton.transform = CGAffineTransformMakeTranslation(320.0f, 0.0f);
_fieldBackground.transform = CGAffineTransformMakeTranslation(320.0f, 0.0f);
}
[UIView animateWithDuration:0.25 delay:0.0 options:options animations:^
{
_inputFieldClippingContainer.alpha = 1.0f;
_fieldBackground.alpha = 1.0f;
_fieldBackground.transform = CGAffineTransformIdentity;
_inputFieldPlaceholder.alpha = 1.0f;
_panelAccessoryView.alpha = 1.0f;
_panelAccessoryView.transform = CGAffineTransformIdentity;
for (UIButton *button in _modeButtons)
{
button.transform = CGAffineTransformIdentity;
}
} completion:nil];
[UIView animateWithDuration:0.25 delay:0 options:options animations:^
{
_attachButton.alpha = 1.0f;
_attachButton.transform = CGAffineTransformIdentity;
} completion:nil];
int animationCurveOption = iosMajorVersion() >= 7 ? (7 << 16) : 0;
[UIView animateWithDuration:0.25 * durationFactor delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState | animationCurveOption animations:^
{
//_recordIndicatorView.alpha = 0.0f;
_recordIndicatorView.transform = CGAffineTransformMakeTranslation(-90.0f, 0.0f);
} completion:^(BOOL finished)
{
if (finished)
{
[_recordIndicatorView removeFromSuperview];
}
}];
[UIView animateWithDuration:0.25 * durationFactor delay:0.05 * durationFactor options:UIViewAnimationOptionBeginFromCurrentState | animationCurveOption animations:^
{
_recordDurationLabel.alpha = 0.0f;
_recordDurationLabel.transform = CGAffineTransformMakeTranslation(-90.0f, 0.0f);
} completion:^(BOOL finished)
{
if (finished)
{
[_recordDurationLabel removeFromSuperview];
}
}];
[UIView animateWithDuration:0.2 * durationFactor delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState | animationCurveOption animations:^
{
_slideToCancelArrow.alpha = 0.0f;
_slideToCancelArrow.transform = CGAffineTransformMakeTranslation(-200, 0.0f);
} completion:^(__unused BOOL finished)
{
}];
[UIView animateWithDuration:0.2 * durationFactor delay:0.05 * durationFactor options:UIViewAnimationOptionBeginFromCurrentState | animationCurveOption animations:^
{
_slideToCancelLabel.alpha = 0.0f;
_slideToCancelLabel.transform = CGAffineTransformMakeTranslation(-200, 0.0f);
} completion:^(__unused BOOL finished)
{
}];
}
#endif
}
- (void)startAudioRecordingTimer
{
_recordDurationLabel.text = @"0:00,00";
_audioRecordingDurationSeconds = 0;
_audioRecordingDurationMilliseconds = 0.0;
_audioRecordingTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(audioTimerEvent) interval:2.0 / 60.0 repeat:false];
}
- (void)audioTimerEvent
{
if (_audioRecordingTimer != nil)
{
[_audioRecordingTimer invalidate];
_audioRecordingTimer = nil;
}
NSTimeInterval recordingDuration = 0.0;
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelAudioRecordingDuration:)])
recordingDuration = [delegate inputPanelAudioRecordingDuration:self];
MTAbsoluteTime currentTime = MTAbsoluteSystemTime();
NSUInteger currentAudioDurationSeconds = (NSUInteger)recordingDuration;
NSUInteger currentAudioDurationMilliseconds = (int)(recordingDuration * 100.0f) % 100;
if (currentAudioDurationSeconds == _audioRecordingDurationSeconds && currentAudioDurationMilliseconds == _audioRecordingDurationMilliseconds)
{
_audioRecordingTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(audioTimerEvent) interval:MAX(0.01, _audioRecordingDurationSeconds + 2.0 / 60.0 - currentTime) repeat:false];
}
else
{
_audioRecordingDurationSeconds = currentAudioDurationSeconds;
_audioRecordingDurationMilliseconds = currentAudioDurationMilliseconds;
_recordDurationLabel.text = [[NSString alloc] initWithFormat:@"%d:%02d,%02d", (int)_audioRecordingDurationSeconds / 60, (int)_audioRecordingDurationSeconds % 60, (int)_audioRecordingDurationMilliseconds];
_audioRecordingTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(audioTimerEvent) interval:2.0 / 60.0 repeat:false];
}
}
- (void)stopAudioRecordingTimer
{
if (_audioRecordingTimer != nil)
{
[_audioRecordingTimer invalidate];
_audioRecordingTimer = nil;
}
}
- (void)audioRecordingStarted
{
[self startAudioRecordingTimer];
}
- (void)audioRecordingFinished
{
[self stopAudioRecordingTimer];
}
- (void)shakeControls
{
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
NSMutableArray *values = [[NSMutableArray alloc] init];
for (NSUInteger i = 0; i < 8; i++)
{
[values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(i % 2 == 0 ? -3.0f : 3.0f, 0.0f, 0.0f)]];
}
[values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0.0f, 0.0f, 0.0f)]];
animation.values = values;
NSMutableArray *keyTimes = [[NSMutableArray alloc] init];
for (NSUInteger i = 0; i < animation.values.count; i++)
[keyTimes addObject:@((NSTimeInterval)i / (animation.values.count - 1.0))];
animation.keyTimes = keyTimes;
animation.duration = 0.5;
[self.layer addAnimation:animation forKey:@"transform"];
_micButton.userInteractionEnabled = false;
TGDispatchAfter(0.5, dispatch_get_main_queue(), ^
{
_micButton.userInteractionEnabled = true;
});
}
- (CGRect)attachmentButtonFrame
{
return _attachButton.frame;
}
- (CGRect)stickerButtonFrame {
if (_stickerModeButton.superview != nil) {
return _stickerModeButton.frame;
} else {
return CGRectZero;
}
}
- (CGRect)broadcastModeButtonFrame {
if (_broadcastButton.superview != nil) {
return _broadcastButton.frame;
} else {
return CGRectZero;
}
}
- (TGModernConversationAssociatedInputPanel *)associatedPanel
{
return _associatedPanel;
}
- (void)setAssociatedPanel:(TGModernConversationAssociatedInputPanel *)associatedPanel animated:(bool)animated
{
if (_associatedPanel != associatedPanel)
{
TGModernConversationAssociatedInputPanel *currentPanel = _associatedPanel;
if (currentPanel != nil)
{
if (animated)
{
[UIView animateWithDuration:0.18 animations:^
{
currentPanel.alpha = 0.0f;
} completion:^(BOOL finished)
{
if (finished)
[currentPanel removeFromSuperview];
}];
}
else
[currentPanel removeFromSuperview];
}
_associatedPanel = associatedPanel;
if (_associatedPanel != nil)
{
__weak TGModernConversationInputTextPanel *weakSelf = self;
_associatedPanel.preferredHeightUpdated = ^
{
__strong TGModernConversationInputTextPanel *strongSelf = weakSelf;
if (strongSelf != nil)
{
strongSelf->_associatedPanel.frame = CGRectMake(0.0f, -[strongSelf->_associatedPanel preferredHeight] + strongSelf->_currentExtendedPanel.frame.size.height, strongSelf.frame.size.width, [strongSelf shouldDisplayPanels] ? [strongSelf->_associatedPanel preferredHeight] : 0.0f);
}
};
_associatedPanel.frame = CGRectMake(0.0f, -[_associatedPanel preferredHeight] + _currentExtendedPanel.frame.size.height, self.frame.size.width, [self shouldDisplayPanels] ? [_associatedPanel preferredHeight] : 0.0f);
[self addSubview:_associatedPanel];
_associatedPanel.alpha = 0.0f;
[self updateAssociatedPanelVisibility:animated];
}
}
}
- (void)updateAssociatedPanelVisibility:(bool)animated {
CGFloat targetAlpha = 1.0f;
if ([_associatedPanel displayForTextEntryOnly]) {
if (![_inputField.internalTextView isFirstResponder] || _inputField.internalTextView.inputView != nil) {
targetAlpha = 0.0f;
}
}
if (ABS(_associatedPanel.alpha - targetAlpha) > FLT_EPSILON) {
if (animated) {
_associatedPanel.alpha = 0.0f;
[UIView animateWithDuration:0.18 animations:^ {
_associatedPanel.alpha = targetAlpha;
}];
} else {
_associatedPanel.alpha = targetAlpha;
}
}
}
- (TGBotReplyMarkup *)currentReplyMarkup
{
if ([_firstExtendedPanel isKindOfClass:[TGModenConcersationReplyAssociatedPanel class]])
{
TGMessage *message = ((TGModenConcersationReplyAssociatedPanel *)_firstExtendedPanel).message;
return message.replyMarkup;
}
return _replyMarkup;
}
- (bool)shouldDisableAutocorrection
{
if ([_firstExtendedPanel isKindOfClass:[TGModernConversationCommandsAssociatedPanel class]])
return true;
return false;
}
- (void)setPrimaryExtendedPanel:(TGModernConversationAssociatedInputPanel *)extendedPanel animated:(bool)animated
{
[self setPrimaryExtendedPanel:extendedPanel animated:animated skipHeightAnimation:false];
}
- (void)setPrimaryExtendedPanel:(TGModernConversationAssociatedInputPanel *)extendedPanel animated:(bool)animated skipHeightAnimation:(bool)skipHeightAnimation
{
TGBotReplyMarkup *previousReplyMarkup = [self currentReplyMarkup];
_firstExtendedPanel = extendedPanel;
if (_secondExtendedPanel == nil)
{
[self _setCurrentExtendedPanel:extendedPanel animated:animated skipHeightAnimation:skipHeightAnimation];
}
if (!TGObjectCompare(previousReplyMarkup, [self currentReplyMarkup]))
[self setCurrentReplyMarkup:[self currentReplyMarkup]];
}
- (void)setSecondaryExtendedPanel:(TGModernConversationAssociatedInputPanel *)extendedPanel animated:(bool)animated
{
[self setSecondaryExtendedPanel:extendedPanel animated:animated skipHeightAnimation:false];
}
- (void)setSecondaryExtendedPanel:(TGModernConversationAssociatedInputPanel *)extendedPanel animated:(bool)animated skipHeightAnimation:(bool)skipHeightAnimation
{
_secondExtendedPanel = extendedPanel;
if (_secondExtendedPanel == nil)
{
if (_firstExtendedPanel != nil)
{
[self _setCurrentExtendedPanel:_firstExtendedPanel animated:animated skipHeightAnimation:skipHeightAnimation];
}
else
{
[self _setCurrentExtendedPanel:nil animated:animated skipHeightAnimation:skipHeightAnimation];
}
}
else
[self _setCurrentExtendedPanel:extendedPanel animated:animated skipHeightAnimation:skipHeightAnimation];
}
- (void)_setCurrentExtendedPanel:(TGModernConversationAssociatedInputPanel *)currentExtendedPanel animated:(bool)animated skipHeightAnimation:(bool)skipHeightAnimation
{
if (_currentExtendedPanel != currentExtendedPanel)
{
bool displayPanels = [self shouldDisplayPanels];
if (animated)
{
UIView *previousExtendedPanel = _currentExtendedPanel;
_currentExtendedPanel = currentExtendedPanel;
if (_currentExtendedPanel != nil)
{
[_currentExtendedPanel setSendAreaWidth:_sendButtonWidth attachmentAreaWidth:[self inputFieldInsets].left];
_currentExtendedPanel.frame = CGRectMake(0.0f, 0.0f, self.frame.size.width, displayPanels ? [_currentExtendedPanel preferredHeight] : 0.0f);
if (previousExtendedPanel != nil)
[self insertSubview:_currentExtendedPanel aboveSubview:previousExtendedPanel];
else
[self insertSubview:_currentExtendedPanel aboveSubview:_backgroundView];
}
_currentExtendedPanel.alpha = 0.0f;
[UIView animateWithDuration:0.2 delay:0 options:0 animations:^
{
previousExtendedPanel.alpha = 0.0f;
_currentExtendedPanel.alpha = 1.0f;
} completion:^(__unused BOOL finished)
{
[previousExtendedPanel removeFromSuperview];
}];
if (!skipHeightAnimation)
{
CGFloat inputContainerHeight = [self heightForInputFieldHeight:_inputField.frame.size.height];
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelWillChangeHeight:height:duration:animationCurve:)])
{
[delegate inputPanelWillChangeHeight:self height:inputContainerHeight duration:0.2 animationCurve:0];
}
}
}
else
{
UIView *previousPrimaryExtendedPanel = _currentExtendedPanel;
_currentExtendedPanel = currentExtendedPanel;
if (_currentExtendedPanel != nil)
{
[_currentExtendedPanel setSendAreaWidth:_sendButtonWidth attachmentAreaWidth:[self inputFieldInsets].left];
_currentExtendedPanel.frame = CGRectMake(0.0f, 0.0f, self.frame.size.width, displayPanels ? [_currentExtendedPanel preferredHeight] : 0.0f);
if (previousPrimaryExtendedPanel != nil)
[self insertSubview:_currentExtendedPanel aboveSubview:previousPrimaryExtendedPanel];
else
[self insertSubview:_currentExtendedPanel aboveSubview:_backgroundView];
}
[previousPrimaryExtendedPanel removeFromSuperview];
if (!skipHeightAnimation)
{
CGFloat inputContainerHeight = [self heightForInputFieldHeight:_inputField.frame.size.height];
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelWillChangeHeight:height:duration:animationCurve:)])
{
[delegate inputPanelWillChangeHeight:self height:inputContainerHeight duration:0.0 animationCurve:0];
}
}
}
}
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
bool sendButtonEnabled = [_inputField.internalTextView.text hasNonWhitespaceCharacters];
if ([delegate respondsToSelector:@selector(inputPanelSendShouldBeAlwaysEnabled:)])
{
if ([delegate inputPanelSendShouldBeAlwaysEnabled:self])
sendButtonEnabled = true;
}
if (_sendButton.enabled != sendButtonEnabled)
_sendButton.enabled = sendButtonEnabled;
[self updateSendButtonVisibility];
}
- (TGModernConversationAssociatedInputPanel *)primaryExtendedPanel
{
return _firstExtendedPanel;
}
- (TGModernConversationAssociatedInputPanel *)secondaryExtendedPanel
{
return _secondExtendedPanel;
}
- (void)setAssociatedStickerList:(NSArray *)stickerList stickerSelected:(void (^)(TGDocumentMediaAttachment *))stickerSelected
{
if (stickerList.count != 0)
{
if ([_associatedPanel isKindOfClass:[TGStickerAssociatedInputPanel class]])
[((TGStickerAssociatedInputPanel *)_associatedPanel) setDocumentList:stickerList];
else
{
TGStickerAssociatedInputPanel *stickerPanel = [[TGStickerAssociatedInputPanel alloc] init];
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
stickerPanel.controller = [delegate inputPanelParentViewController:self];
stickerPanel.documentSelected = stickerSelected;
[stickerPanel setDocumentList:stickerList];
[stickerPanel setTargetOffset:113.0f];
[self setAssociatedPanel:stickerPanel animated:true];
}
}
else
{
[self setAssociatedPanel:nil animated:true];
}
}
- (void)growingTextView:(HPGrowingTextView *)__unused growingTextView didPasteImages:(NSArray *)images
{
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelRequestedSendImages:images:)])
[delegate inputPanelRequestedSendImages:self images:images];
}
- (void)growingTextView:(HPGrowingTextView *)__unused growingTextView didPasteData:(NSData *)data
{
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelRequestedSendData:data:)])
[delegate inputPanelRequestedSendData:self data:data];
}
- (void)growingTextView:(HPGrowingTextView *)__unused growingTextView receivedReturnKeyCommandWithModifierFlags:(UIKeyModifierFlags)flags
{
if (flags & UIKeyModifierAlternate)
[self addNewLine];
else
[self sendButtonPressed];
}
- (void)addNewLine
{
self.inputField.text = [NSString stringWithFormat:@"%@\n", self.inputField.text];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (_inputDisabled) {
return nil;
}
for (UIButton *button in _modeButtons)
{
if (!button.hidden && CGRectContainsPoint(button.frame, point))
return button;
}
if (_associatedPanel != nil)
{
UIView *result = [_associatedPanel hitTest:[self convertPoint:point toView:_associatedPanel] withEvent:event];
if (result != nil)
return result;
}
if (_currentExtendedPanel != nil)
{
UIView *result = [_currentExtendedPanel hitTest:[self convertPoint:point toView:_currentExtendedPanel] withEvent:event];
if (result != nil)
return result;
}
if (CGRectContainsPoint(_fieldBackground.frame, point) && self.maybeInputField.isFirstResponder && self.maybeInputField.internalTextView.inputView != nil)
{
return _fieldBackground;
}
return [super hitTest:point withEvent:event];
}
- (void)adjustCustomKeyboardForWidth:(CGFloat)width
{
[_stickerKeyboardView sizeToFitForWidth:width];
[_stickerKeyboardView.superview setNeedsLayout];
}
- (void)setModeButtons:(NSArray *)modeButtons
{
if ([_modeButtons isEqualToArray:modeButtons])
return;
for (UIButton *button in _modeButtons)
{
[button removeFromSuperview];
button.transform = CGAffineTransformIdentity;
}
_modeButtons = modeButtons;
CGFloat inset = 4.0f;
for (UIButton *button in _modeButtons)
{
if (_overlayDisabledView.superview != nil) {
[self insertSubview:button belowSubview:_overlayDisabledView];
} else {
[self addSubview:button];
}
inset += button.frame.size.width + 9.0f;
}
if (iosMajorVersion() >= 7) {
UIEdgeInsets insets = _inputField.internalTextView.textContainerInset;
if (ABS(inset - insets.right) > FLT_EPSILON) {
insets.right = inset;
_inputField.internalTextView.textContainerInset = insets;
[_inputField refreshHeight:false];
}
}
[self setNeedsLayout];
}
- (void)keyboardModeButtonPressed
{
if (self.maybeInputField.internalTextView.inputView == nil)
return;
if (self.maybeInputField.isFirstResponder)
{
_changingKeyboardMode = true;
[self.inputField resignFirstResponder];
}
self.inputField.internalTextView.inputView = nil;
_stickerKeyboardView.enableAnimation = false;
self.inputField.internalTextView.enableFirstResponder = true;
[self.inputField becomeFirstResponder];
_changingKeyboardMode = false;
[self updateModeButtonVisibility];
[self updateAssociatedPanelVisibility:true];
if (_replyMarkup != nil) {
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelRequestedToggleCommandKeyboard:showCommandKeyboard:)]) {
[delegate inputPanelRequestedToggleCommandKeyboard:self showCommandKeyboard:false];
}
}
}
- (void)stickerModeButtonPressed
{
if ([self.inputField.internalTextView.inputView isKindOfClass:[TGStickerKeyboardView class]])
return;
if (self.maybeInputField.isFirstResponder)
{
_changingKeyboardMode = true;
[self.inputField resignFirstResponder];
}
if (_stickerKeyboardView == nil)
{
_stickerKeyboardView = [[TGStickerKeyboardView alloc] init];
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
_stickerKeyboardView.parentViewController = [delegate inputPanelParentViewController:self];
__weak TGModernConversationInputTextPanel *weakSelf = self;
_stickerKeyboardView.stickerSelected = ^(TGDocumentMediaAttachment *sticker)
{
__strong TGModernConversationInputTextPanel *strongSelf = weakSelf;
if (strongSelf != nil)
{
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)strongSelf.delegate;
[delegate inputPanelRequestedSendSticker:strongSelf sticker:sticker];
}
};
_stickerKeyboardView.gifSelected = ^(TGDocumentMediaAttachment *sticker)
{
__strong TGModernConversationInputTextPanel *strongSelf = weakSelf;
if (strongSelf != nil)
{
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)strongSelf.delegate;
[delegate inputPanelRequestedSendGif:strongSelf document:sticker];
}
};
_stickerKeyboardView.gifTabActive = ^(bool isActive) {
__strong TGModernConversationInputTextPanel *strongSelf = weakSelf;
if (strongSelf != nil) {
[strongSelf updateGifsTabActive:isActive];
}
};
[_stickerKeyboardView sizeToFitForWidth:self.frame.size.width];
}
else
[_stickerKeyboardView updateIfNeeded];
self.inputField.internalTextView.inputView = _stickerKeyboardView;
_stickerKeyboardView.enableAnimation = true;
self.inputField.internalTextView.enableFirstResponder = true;
[self.inputField becomeFirstResponder];
_changingKeyboardMode = false;
[self updateModeButtonVisibility];
[self updateAssociatedPanelVisibility:true];
}
- (void)showStickersPanel
{
[self stickerModeButtonPressed];
}
- (void)updateGifsTabActive:(bool)isActive {
if (isActive) {
if (_inputField.text.length == 0) {
[_inputField setText:@"@gif " animated:true];
_removeGifTabTextOnDeactivation = true;
}
} else {
if ([_inputField.text isEqualToString:@"@gif "]) {
[_inputField setText:@"" animated:true];
}
}
}
- (void)slashModeButtonPressed
{
if (self.maybeInputField.text.length == 0)
{
self.inputField.internalTextView.enableFirstResponder = true;
self.inputField.text = @"/";
[self updateModeButtonVisibility];
}
}
- (void)commandModeButtonPressed
{
if ([self.inputField.internalTextView.inputView isKindOfClass:[TGCommandKeyboardView class]])
return;
if (self.maybeInputField.isFirstResponder)
{
_changingKeyboardMode = true;
[self.inputField resignFirstResponder];
}
TGCommandKeyboardView *commandKeyboardView = [[TGCommandKeyboardView alloc] init];
if ([self currentReplyMarkup].matchDefaultHeight)
{
commandKeyboardView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
[commandKeyboardView setReplyMarkup:[self currentReplyMarkup]];
__weak TGModernConversationInputTextPanel *weakSelf = self;
commandKeyboardView.commandActivated = ^(NSString *command, int32_t userId, int32_t messageId)
{
__strong TGModernConversationInputTextPanel *strongSelf = weakSelf;
if (strongSelf != nil)
{
if ([strongSelf currentReplyMarkup].hideKeyboardOnActivation)
[strongSelf keyboardModeButtonPressed];
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)strongSelf.delegate;
if ([delegate respondsToSelector:@selector(inputPanelRequestedActivateCommand:command:userId:messageId:)])
{
[delegate inputPanelRequestedActivateCommand:self command:command userId:userId messageId:messageId];
}
}
};
CGSize size = [commandKeyboardView sizeThatFits:self.frame.size];
commandKeyboardView.frame = CGRectMake(0.0f, 0.0f, size.width, size.height);
self.inputField.internalTextView.inputView = commandKeyboardView;
_stickerKeyboardView.enableAnimation = false;
[self updateGifsTabActive:false];
self.inputField.internalTextView.enableFirstResponder = true;
[self.inputField becomeFirstResponder];
_changingKeyboardMode = false;
[self updateModeButtonVisibility];
[self updateAssociatedPanelVisibility:true];
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelRequestedToggleCommandKeyboard:showCommandKeyboard:)]) {
[delegate inputPanelRequestedToggleCommandKeyboard:self showCommandKeyboard:true];
}
}
- (void)growingTextViewDidEndEditing:(HPGrowingTextView *)__unused growingTextView
{
if (!_changingKeyboardMode)
{
if (self.maybeInputField != nil && ![self.maybeInputField.text hasNonWhitespaceCharacters])
self.maybeInputField.text = nil;
self.maybeInputField.internalTextView.inputView = nil;
_stickerKeyboardView.enableAnimation = false;
[self updateModeButtonVisibility];
if (_removeGifTabTextOnDeactivation) {
[self updateGifsTabActive:false];
}
[self updateAssociatedPanelVisibility:true];
}
}
- (void)setReplyMarkup:(TGBotReplyMarkup *)replyMarkup
{
if (!TGObjectCompare(_replyMarkup, replyMarkup))
{
_replyMarkup = replyMarkup;
[self setCurrentReplyMarkup:[self currentReplyMarkup]];
}
}
- (void)setHasBots:(bool)hasBots
{
if (_hasBots != hasBots)
{
_hasBots = hasBots;
[self updateModeButtonVisibility];
}
}
- (void)setCanBroadcast:(bool)canBroadcast {
if (_canBroadcast != canBroadcast) {
_canBroadcast = canBroadcast;
[self updateModeButtonVisibility];
[self _updatePlaceholderImage];
}
}
- (void)setIsBroadcasting:(bool)isBroadcasting {
if (_isBroadcasting != isBroadcasting) {
_isBroadcasting = isBroadcasting;
UIImage *broadcastImage = _isBroadcasting ? [UIImage imageNamed:@"ConversationInputFieldBroadcastIconActive.png"] : [UIImage imageNamed:@"ConversationInputFieldBroadcastIconInactive.png"];
[_broadcastButton setImage:broadcastImage forState:UIControlStateNormal];
[self _updatePlaceholderImage];
[self updateModeButtonVisibility];
}
}
- (void)setIsAlwaysBroadcasting:(bool)isAlwaysBroadcasting {
if (_isAlwaysBroadcasting != isAlwaysBroadcasting) {
_isAlwaysBroadcasting = isAlwaysBroadcasting;
[self updateModeButtonVisibility];
[self _updatePlaceholderImage];
}
}
- (void)setIsChannel:(bool)isChannel {
if (_isChannel != isChannel) {
_isChannel = isChannel;
[self _updatePlaceholderImage];
}
}
- (void)setInputDisabled:(bool)inputDisabled {
if (_inputDisabled != inputDisabled) {
_inputDisabled = inputDisabled;
if (inputDisabled) {
if (_overlayDisabledView == nil) {
_overlayDisabledView = [[UIView alloc] init];
_overlayDisabledView.backgroundColor = UIColorRGBA(0xf7f7f7, 0.5f);
}
if (_overlayDisabledView.superview == nil) {
[self addSubview:_overlayDisabledView];
}
self.userInteractionEnabled = false;
} else {
if (_overlayDisabledView.superview != nil) {
[_overlayDisabledView removeFromSuperview];
}
self.userInteractionEnabled = true;
}
[self _updatePlaceholderImage];
[self setNeedsLayout];
}
}
- (void)setCurrentReplyMarkup:(TGBotReplyMarkup *)replyMarkup
{
if ([self.maybeInputField.internalTextView.inputView isKindOfClass:[TGCommandKeyboardView class]])
{
if (replyMarkup == nil || replyMarkup.rows.count == 0)
[self keyboardModeButtonPressed];
else
{
_changingKeyboardMode = true;
TGCommandKeyboardView *commandKeyboardView = [[TGCommandKeyboardView alloc] init];
if (replyMarkup.matchDefaultHeight)
{
commandKeyboardView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
[commandKeyboardView setReplyMarkup:replyMarkup];
__weak TGModernConversationInputTextPanel *weakSelf = self;
commandKeyboardView.commandActivated = ^(NSString *command, int32_t userId, int32_t messageId)
{
__strong TGModernConversationInputTextPanel *strongSelf = weakSelf;
if (strongSelf != nil)
{
if ([strongSelf currentReplyMarkup].hideKeyboardOnActivation)
[strongSelf keyboardModeButtonPressed];
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)strongSelf.delegate;
if ([delegate respondsToSelector:@selector(inputPanelRequestedActivateCommand:command:userId:messageId:)])
[delegate inputPanelRequestedActivateCommand:self command:command userId:userId messageId:messageId];
}
};
CGSize size = [commandKeyboardView sizeThatFits:self.frame.size];
commandKeyboardView.frame = CGRectMake(0.0f, 0.0f, size.width, size.height);
self.inputField.internalTextView.inputView = commandKeyboardView;
_stickerKeyboardView.enableAnimation = false;
[self.inputField.internalTextView reloadInputViews];
[self updateGifsTabActive:false];
[self updateAssociatedPanelVisibility:true];
_changingKeyboardMode = false;
}
}
else if (self.maybeInputField.text.length == 0 && replyMarkup != nil && replyMarkup.rows.count != 0)
{
if (![self.maybeInputField.internalTextView.inputView isKindOfClass:[TGStickerKeyboardView class]])
{
if (_enableKeyboard)
[self commandModeButtonPressed];
else
_shouldShowKeyboardAutomatically = true;
}
}
[self updateModeButtonVisibility];
}
- (void)setEnableKeyboard:(bool)enableKeyboard
{
if (!_enableKeyboard)
{
_enableKeyboard = enableKeyboard;
if (_canShowKeyboardAutomatically && _shouldShowKeyboardAutomatically && [self currentReplyMarkup] != nil && (![self currentReplyMarkup].hideKeyboardOnActivation || ![self currentReplyMarkup].alreadyActivated) && ![self currentReplyMarkup].manuallyHidden)
{
[self commandModeButtonPressed];
}
}
else
_enableKeyboard = enableKeyboard;
_shouldShowKeyboardAutomatically = false;
}
- (void)broadcastButtonPressed {
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelToggleBroadcastMode:)]) {
[delegate inputPanelToggleBroadcastMode:self];
}
}
- (void)setDisplayProgress:(bool)displayProgress {
if (_displayProgress != displayProgress) {
_displayProgress = displayProgress;
if (_displayProgress) {
[_progressButtonIndicator startAnimating];
} else {
[_progressButtonIndicator stopAnimating];
}
[self updateModeButtonVisibility];
}
}
- (void)setContextPlaceholder:(NSString *)contextPlaceholder {
if (!TGStringCompare(_contextPlaceholder, contextPlaceholder)) {
_contextPlaceholder = contextPlaceholder;
if (contextPlaceholder.length == 0) {
[_contextPlaceholderLabel removeFromSuperview];
} else {
if (_contextPlaceholderLabel == nil) {
_contextPlaceholderLabel = [[UILabel alloc] init];
_contextPlaceholderLabel.textColor = UIColorRGB(0xbebec0);
_contextPlaceholderLabel.backgroundColor = [UIColor clearColor];
_contextPlaceholderLabel.font = TGSystemFontOfSize(16);
}
if (_contextPlaceholderLabel.superview == nil) {
[_fieldBackground insertSubview:_contextPlaceholderLabel aboveSubview:_inputFieldPlaceholder];
}
_contextPlaceholderLabel.text = contextPlaceholder;
[self setNeedsLayout];
}
}
}
- (void)animateRecordingIn {
[_micButton animateIn];
}
- (void)addMicLevel:(CGFloat)level {
[_micButton addMicLevel:level];
}
- (void)setMessageEditingContext:(TGMessageEditingContext *)messageEditingContext {
[self setMessageEditingContext:messageEditingContext animated:false];
}
- (void)setMessageEditingContext:(TGMessageEditingContext *)messageEditingContext animated:(bool)animated {
if (messageEditingContext != _messageEditingContext) {
_messageEditingContext = messageEditingContext;
_messageEditingContextInvalidated = false;
NSString *sendText = TGLocalized(@"Conversation.Send");
if (_messageEditingContext != nil) {
sendText = TGLocalized(@"Conversation.LinkDialogSave");
}
_sendButtonWidth = MIN(150.0f, [sendText sizeWithFont:TGMediumSystemFontOfSize(17)].width + 8.0f);
[_sendButton setTitle:sendText forState:UIControlStateNormal];
[self updateModeButtonVisibility];
[self updateSendButtonVisibility];
[self _updatePlaceholderImage];
[self updateInputFieldLayout];
[self setNeedsLayout];
[self layoutSubviews];
[self.inputField setText:messageEditingContext.text animated:animated];
}
}
- (TGMessageEditingContext *)messageEditingContext {
_messageEditingContextInvalidated = true;
if (_messageEditingContextInvalidated) {
if (_messageEditingContext != nil) {
_messageEditingContext = [[TGMessageEditingContext alloc] initWithText:self.inputField.text isCaption:_messageEditingContext.isCaption messageId:_messageEditingContext.messageId];
}
_messageEditingContextInvalidated = false;
}
return _messageEditingContext;
}
@end