1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-03 09:57:46 +01:00
Telegram/Telegraph/TGModernConversationInputTextPanel.m
2014-07-10 18:11:09 +04:00

932 lines
38 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 <MTProtoKit/MTTime.h>
#import "TGHacks.h"
#import "TGFont.h"
#import "TGImageUtils.h"
#import "TGTimerTarget.h"
#import "TGViewController.h"
#import "HPGrowingTextView.h"
#import "HPTextViewInternal.h"
#import "TGModernButton.h"
#import "TGModernConversationInputMicButton.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;
}
@interface TGModernConversationInputTextPanel () <HPGrowingTextViewDelegate, TGModernConversationInputMicButtonDelegate>
{
CALayer *_stripeLayer;
UIView *_backgroundView;
CGFloat _sendButtonWidth;
#if TG_ENABLE_AUDIO_NOTES
TGModernConversationInputMicButton *_micButton;
UIImageView *_micButtonIconView;
#endif
UIView *_audioRecordingContainer;
NSUInteger _audioRecordingDurationSeconds;
NSTimer *_audioRecordingTimer;
UIImageView *_recordIndicatorView;
UILabel *_recordDurationLabel;
UIImageView *_slideToCancelArrow;
UILabel *_slideToCancelLabel;
CFAbsoluteTime _recordingInterfaceShowTime;
}
@end
@implementation TGModernConversationInputTextPanel
- (instancetype)initWithFrame:(CGRect)frame accessoryView:(UIView *)panelAccessoryView
{
self = [super initWithFrame:frame];
if (self)
{
_sendButtonWidth = MIN(100.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 UIImage *placeholderImage = nil;
static int localizationVersion = 0;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
fieldBackgroundImage = [[UIImage imageNamed:@"ModernConversationInput.png"] stretchableImageWithLeftCapWidth:8 topCapHeight:8];
attachImage = [UIImage imageNamed:@"ModernConversationAttach.png"];
});
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();
localizationVersion = TGLocalizedStaticVersion;
}
_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, _panelAccessoryView.frame.size.width, _panelAccessoryView.frame.size.height));
[self addSubview:_panelAccessoryView];
CGPoint placeholderOffset = [self inputFieldPlaceholderOffset];
_inputFieldPlaceholder = [[UIImageView alloc] initWithImage:placeholderImage];
setViewFrame(_inputFieldPlaceholder, CGRectOffset(_inputFieldPlaceholder.frame, placeholderOffset.x, placeholderOffset.y));
[_fieldBackground addSubview:_inputFieldPlaceholder];
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 addSubview:_micButtonIconView];
[self addSubview:_micButton];
[self updateSendButtonVisibility];
}
return self;
}
- (void)dealloc
{
[self stopAudioRecordingTimer];
}
- (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 _maxNumberOfLinesForInterfaceOrientation:_interfaceOrientation];
_inputField.delegate = self;
_inputField.internalTextView.scrollIndicatorInsets = UIEdgeInsetsMake(-inputFieldInternalEdgeInsets.top, 0, 5 - TGRetinaPixel, 0);
[_inputFieldClippingContainer addSubview:_inputField];
}
return _inputField;
}
- (void)growingTextViewDidChange:(HPGrowingTextView *)__unused growingTextView
{
int textLength = growingTextView.text.length;
NSString *text = growingTextView.text;
bool hasNonWhitespace = false;
for (int i = 0; i < textLength; i++)
{
unichar c = [text characterAtIndex:i];
if (c != ' ' && c != '\n' && c != '\t')
{
hasNonWhitespace = true;
break;
}
}
if (_sendButton.enabled != hasNonWhitespace)
_sendButton.enabled = hasNonWhitespace;
[self updateSendButtonVisibility];
if (hasNonWhitespace)
{
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputTextPanelHasIndicatedTypingActivity:)])
[delegate inputTextPanelHasIndicatedTypingActivity:self];
}
}
- (void)updateSendButtonVisibility
{
#if TG_ENABLE_AUDIO_NOTES
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;
}
_sendButton.hidden = hidden;
_micButton.hidden = !_sendButton.hidden;
#endif
}
- (void)fieldBackgroundTapGesture:(UITapGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateRecognized)
{
[self inputField].internalTextView.enableFirstResponder = true;
[[self inputField].internalTextView becomeFirstResponder];
}
}
- (void)sendButtonPressed
{
if (_inputField.internalTextView.isFirstResponder)
[TGHacks applyCurrentKeyboardAutocorrectionVariant];
NSMutableString *text = [[NSMutableString alloc] initWithString:_inputField.text];
int textLength = 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;
}
if (text.length != 0)
{
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
{
[self setShowRecordingInterface:true velocity:0.0f];
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelAudioRecordingStart:)])
[delegate inputPanelAudioRecordingStart:self];
}
- (void)micButtonInteractionCancelled:(CGFloat)velocity
{
[self setShowRecordingInterface:false velocity:velocity];
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelAudioRecordingCancel:)])
[delegate inputPanelAudioRecordingCancel:self];
}
- (void)micButtonInteractionCompleted:(CGFloat)velocity
{
[self setShowRecordingInterface:false velocity:velocity];
id<TGModernConversationInputTextPanelDelegate> delegate = (id<TGModernConversationInputTextPanelDelegate>)self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelAudioRecordingComplete:)])
[delegate inputPanelAudioRecordingComplete:self];
}
- (void)micButtonInteractionUpdate:(float)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(([UIScreen mainScreen].bounds.size.width - labelWidth) / 2.0f) - 9.0f - 6.0f;
CGFloat timerWidth = 70.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)_maxNumberOfLinesForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
return UIInterfaceOrientationIsLandscape(orientation) ? 3 : ([TGViewController isWidescreen] ? 7 : 5);
}
- (void)adjustForOrientation:(UIInterfaceOrientation)orientation keyboardHeight:(float)keyboardHeight duration:(NSTimeInterval)duration animationCurve:(int)animationCurve
{
if (UIInterfaceOrientationIsPortrait(_interfaceOrientation) != UIInterfaceOrientationIsPortrait(orientation))
[self changeOrientationToOrientation:orientation keyboardHeight:keyboardHeight duration:0.0];
_interfaceOrientation = orientation;
[self _adjustForOrientation:orientation 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;
}
- (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;
}
- (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)_adjustForOrientation:(UIInterfaceOrientation)orientation keyboardHeight:(float)keyboardHeight inputFieldHeight:(float)inputFieldHeight duration:(NSTimeInterval)duration animationCurve:(int)animationCurve
{
dispatch_block_t block = ^
{
id<TGModernConversationInputPanelDelegate> delegate = self.delegate;
CGSize messageAreaSize = [delegate messageAreaSizeForInterfaceOrientation:orientation];
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)changeOrientationToOrientation:(UIInterfaceOrientation)orientation keyboardHeight:(float)keyboardHeight duration:(NSTimeInterval)duration
{
_interfaceOrientation = orientation;
id<TGModernConversationInputPanelDelegate> delegate = self.delegate;
CGSize messageAreaSize = [delegate messageAreaSizeForInterfaceOrientation:orientation];
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:^
{
NSRange range = _inputField.internalTextView.selectedRange;
_inputField.delegate = 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)];
*/
UIEdgeInsets inputFieldInsets = [self inputFieldInsets];
UIEdgeInsets inputFieldInternalEdgeInsets = [self inputFieldInternalEdgeInsets];
CGRect inputFieldClippingFrame = CGRectMake(inputFieldInsets.left, inputFieldInsets.top, messageAreaSize.width - inputFieldInsets.left - inputFieldInsets.right - _sendButtonWidth - 1, 0.0f);
CGRect inputFieldFrame = CGRectMake(inputFieldInternalEdgeInsets.left, inputFieldInternalEdgeInsets.top, inputFieldClippingFrame.size.width - inputFieldInternalEdgeInsets.left, 0.0f);
setViewFrame(_inputField, inputFieldFrame);
[_inputField setMaxNumberOfLines:[self _maxNumberOfLinesForInterfaceOrientation:orientation]];
[_inputField refreshHeight];
_inputField.internalTextView.selectedRange = range;
_inputField.delegate = self;
}];
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:(float)height duration:(NSTimeInterval)duration animationCurve:(int)animationCurve
{
UIEdgeInsets inputFieldInsets = [self inputFieldInsets];
CGFloat inputContainerHeight = MAX([self baseHeight], height - 8 + inputFieldInsets.top + inputFieldInsets.bottom);
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;
_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);
UIEdgeInsets inputFieldInsets = [self inputFieldInsets];
setViewFrame(_fieldBackground, CGRectMake(inputFieldInsets.left, inputFieldInsets.top, frame.size.width - inputFieldInsets.left - inputFieldInsets.right - _sendButtonWidth - 1, frame.size.height - inputFieldInsets.top - inputFieldInsets.bottom));
if (_panelAccessoryView != nil)
{
setViewFrame(_panelAccessoryView, CGRectMake(CGRectGetMaxX(_fieldBackground.frame) - _panelAccessoryView.frame.size.width, _fieldBackground.frame.origin.y, _panelAccessoryView.frame.size.width, _panelAccessoryView.frame.size.height));
}
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, _sendButton.frame);
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, _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), CGFloor((self.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, CGFloor((self.frame.size.height - slideToCancelLabelFrame.size.height) / 2.0f) + 1.0f, slideToCancelArrowFrame.size.width, slideToCancelArrowFrame.size.height));
}
#endif
setViewFrame(_audioRecordingContainer, self.bounds);
}
- (void)setShowRecordingInterface:(bool)show velocity:(CGFloat)velocity
{
#if TG_ENABLE_AUDIO_NOTES
if (show)
{
_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];
setViewFrame(_recordIndicatorView, CGRectMake(11.0f, CGFloor(([self baseHeight] - 9.0f) / 2.0f) + (TGIsPad() ? 1.0f : 0.0f), 9.0f, 9.0f));
_recordIndicatorView.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
_recordIndicatorView.alpha = 0.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";
[_recordDurationLabel sizeToFit];
_recordDurationLabel.alpha = 0.0f;
_recordDurationLabel.layer.anchorPoint = CGPointMake((26.0f - _recordDurationLabel.frame.size.width) / (2 * 26.0f), 0.5f);
setViewFrame(_recordDurationLabel, CGRectMake(26.0f, CGFloor(([self baseHeight] - _recordDurationLabel.frame.size.height) / 2.0f), 60.0f, _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";
if (_recordIndicatorView.superview == nil)
[_audioRecordingContainer addSubview:_recordIndicatorView];
if (_recordDurationLabel.superview == nil)
[_audioRecordingContainer addSubview:_recordDurationLabel];
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);
_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];
}
else
{
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;
} 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";
_audioRecordingDurationSeconds = 0;
_audioRecordingTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(audioTimerEvent) interval:1.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;
if (currentAudioDurationSeconds == _audioRecordingDurationSeconds)
{
_audioRecordingTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(audioTimerEvent) interval:MAX(0.01, _audioRecordingDurationSeconds + 1.0 - currentTime) repeat:false];
}
else
{
_audioRecordingDurationSeconds = currentAudioDurationSeconds;
_recordDurationLabel.text = [[NSString alloc] initWithFormat:@"%d:%02d", (int)_audioRecordingDurationSeconds / 60, (int)_audioRecordingDurationSeconds % 60];
_audioRecordingTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(audioTimerEvent) interval:1.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;
}
- (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];
}
@end