/* * 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 #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 () { id _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 delegate = (id)self.delegate; if ([delegate respondsToSelector:@selector(isInputPanelTextEnabled:)]) return [delegate isInputPanelTextEnabled:self]; return true; } - (void)growingTextViewDidBeginEditing:(HPGrowingTextView *)__unused growingTextView { id delegate = (id)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 delegate = (id)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 delegate = (id)self.delegate; if ([delegate respondsToSelector:@selector(inputTextPanelHasIndicatedTypingActivity:)]) [delegate inputTextPanelHasIndicatedTypingActivity:self]; } } if (!hasNonWhitespace) { id delegate = (id)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 delegate = (id)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 delegate = (id)self.delegate; if ([delegate respondsToSelector:@selector(inputPanelSendShouldBeAlwaysEnabled:)]) { if ([delegate inputPanelSendShouldBeAlwaysEnabled:self]) enableSend = true; } if (_messageEditingContext != nil && _messageEditingContext.isCaption) { enableSend = true; } if (enableSend) { id delegate = (id)self.delegate; if ([delegate respondsToSelector:@selector(inputPanelRequestedSendMessage:text:)]) [delegate inputPanelRequestedSendMessage:self text:text]; } } - (void)attachButtonPressed { id delegate = (id)self.delegate; if ([delegate respondsToSelector:@selector(inputPanelRequestedAttachmentsMenu:)]) [delegate inputPanelRequestedAttachmentsMenu:self]; } - (void)micButtonInteractionBegan { id delegate = (id)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 delegate = (id)self.delegate; if ([delegate respondsToSelector:@selector(inputPanelAudioRecordingCancel:)]) [delegate inputPanelAudioRecordingCancel:self]; } - (void)micButtonInteractionCompleted:(CGFloat)velocity { if (_recording) { _recording = false; [self setShowRecordingInterface:false velocity:velocity]; } id delegate = (id)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 delegate = (id)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 delegate = (id)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 delegate = (id)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 delegate = (id)self.delegate; if ([delegate respondsToSelector:@selector(inputPanelWillChangeHeight:height:duration:animationCurve:)]) { [delegate inputPanelWillChangeHeight:self height:inputContainerHeight duration:0.0 animationCurve:0]; } } } } id delegate = (id)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 delegate = (id)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 delegate = (id)self.delegate; if ([delegate respondsToSelector:@selector(inputPanelRequestedSendImages:images:)]) [delegate inputPanelRequestedSendImages:self images:images]; } - (void)growingTextView:(HPGrowingTextView *)__unused growingTextView didPasteData:(NSData *)data { id delegate = (id)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 delegate = (id)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 delegate = (id)self.delegate; _stickerKeyboardView.parentViewController = [delegate inputPanelParentViewController:self]; __weak TGModernConversationInputTextPanel *weakSelf = self; _stickerKeyboardView.stickerSelected = ^(TGDocumentMediaAttachment *sticker) { __strong TGModernConversationInputTextPanel *strongSelf = weakSelf; if (strongSelf != nil) { id delegate = (id)strongSelf.delegate; [delegate inputPanelRequestedSendSticker:strongSelf sticker:sticker]; } }; _stickerKeyboardView.gifSelected = ^(TGDocumentMediaAttachment *sticker) { __strong TGModernConversationInputTextPanel *strongSelf = weakSelf; if (strongSelf != nil) { id delegate = (id)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 delegate = (id)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 delegate = (id)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 delegate = (id)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 delegate = (id)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