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

710 lines
24 KiB
Objective-C

#import "TGNotificationReplyPanelView.h"
#import "TGFont.h"
#import "TGImageUtils.h"
#import "TGStringUtils.h"
#import "TGViewController.h"
#import "TGAppDelegate.h"
#import "TGModernButton.h"
#import "HPGrowingTextView.h"
#import "HPTextViewInternal.h"
#import "TGModernConversationInputTextPanel.h"
#import "TGModernConversationAssociatedInputPanel.h"
#import "TGStickerAssociatedInputPanel.h"
#import "TGStickerKeyboardView.h"
@interface TGNotificationReplyPanelView () <HPGrowingTextViewDelegate>
{
UIView *_wrapperView;
UIView *_separatorView;
UIView *_fieldBackground;
TGModernButton *_keyboardModeButton;
TGModernButton *_stickerModeButton;
TGModernButton *_sendButton;
UIView *_inputFieldClippingContainer;
HPGrowingTextView *_inputField;
CGFloat _sendButtonWidth;
NSArray *_modeButtons;
TGModernConversationAssociatedInputPanel *_associatedPanel;
TGStickerKeyboardView *_stickerKeyboardView;
bool _notIdle;
SMetaDisposable *_stickerPacksDisposable;
}
@property (nonatomic, readonly) bool changingKeyboardMode;
@end
@implementation TGNotificationReplyPanelView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
_sendButtonWidth = MIN(100.0f, [TGLocalized(@"Conversation.Send") sizeWithFont:TGMediumSystemFontOfSize(17)].width + 8.0f);
_wrapperView = [[UIView alloc] initWithFrame:self.bounds];
_wrapperView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:_wrapperView];
CGFloat thickness = TGIsRetina() ? 0.5f : 1.0f;
_separatorView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, thickness)];
_separatorView.alpha = 0.7f;
_separatorView.backgroundColor = UIColorRGB(0xb2b2b2);
[_wrapperView addSubview:_separatorView];
_fieldBackground = [[UIView alloc] initWithFrame:CGRectZero];
_fieldBackground.alpha = 0.82f;
_fieldBackground.backgroundColor = UIColorRGB(0x666666);
_fieldBackground.layer.cornerRadius = 5;
[_wrapperView addSubview:_fieldBackground];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleFieldBackgroundTap:)];
[_fieldBackground addGestureRecognizer:tapGestureRecognizer];
static dispatch_once_t onceToken;
static UIImage *stickerImage;
static UIImage *keyboardImage;
dispatch_once(&onceToken, ^
{
stickerImage = TGTintedImage([UIImage imageNamed:@"ConversationInputFieldStickerIcon.png"], UIColorRGB(0xffffff));
keyboardImage = TGTintedImage([UIImage imageNamed:@"ConversationInputFieldKeyboardIcon.png"], UIColorRGB(0xffffff));
});
_stickerModeButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 28, 28)];
_stickerModeButton.adjustsImageWhenHighlighted = false;
_stickerModeButton.exclusiveTouch = true;
[_stickerModeButton setImage:stickerImage forState:UIControlStateNormal];
[_stickerModeButton addTarget:self action:@selector(stickerModeButtonPressed) forControlEvents:UIControlEventTouchUpInside];
_keyboardModeButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 28, 28)];
_keyboardModeButton.adjustsImageWhenHighlighted = false;
_keyboardModeButton.exclusiveTouch = true;
[_keyboardModeButton setImage:keyboardImage forState:UIControlStateNormal];
[_keyboardModeButton addTarget:self action:@selector(keyboardModeButtonPressed) forControlEvents:UIControlEventTouchUpInside];
_sendButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, _sendButtonWidth, self.frame.size.height)];
_sendButton.enabled = false;
_sendButton.titleLabel.font = TGMediumSystemFontOfSize(17);
[_sendButton setTitle:TGLocalized(@"Conversation.Send") forState:UIControlStateNormal];
[_sendButton setTitleColor:[UIColor whiteColor]];
[_sendButton addTarget:self action:@selector(sendButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_sendButton];
[self updateModeButtonVisibilityForce:false];
}
return self;
}
#pragma mark -
- (HPGrowingTextView *)inputField
{
if (_inputField != nil)
return _inputField;
CGRect clippingFrame = _fieldBackground.frame;
_inputFieldClippingContainer = [[UIView alloc] initWithFrame:clippingFrame];
_inputFieldClippingContainer.clipsToBounds = true;
[_wrapperView addSubview:_inputFieldClippingContainer];
UIEdgeInsets internalInsets = [self _inputFieldInternalEdgeInsets];
_inputField = [[HPGrowingTextView alloc] initWithFrame:CGRectMake(internalInsets.left, internalInsets.top + TGRetinaPixel, _inputFieldClippingContainer.frame.size.width - internalInsets.left - internalInsets.right, _inputFieldClippingContainer.frame.size.height)];
_inputField.font = TGSystemFontOfSize(16);
_inputField.clipsToBounds = true;
_inputField.backgroundColor = nil;
_inputField.opaque = false;
_inputField.textColor = [UIColor whiteColor];
_inputField.internalTextView.backgroundColor = nil;
_inputField.internalTextView.opaque = false;
_inputField.internalTextView.contentMode = UIViewContentModeLeft;
if (iosMajorVersion() >= 7)
_inputField.internalTextView.keyboardAppearance = UIKeyboardAppearanceDark;
else
_inputField.internalTextView.keyboardAppearance = UIKeyboardAppearanceAlert;
_inputField.maxNumberOfLines = 4;
_inputField.delegate = self;
_inputField.internalTextView.enableFirstResponder = true;
_inputField.internalTextView.scrollIndicatorInsets = UIEdgeInsetsMake(-internalInsets.top, 0, 5 - TGRetinaPixel, 0);
[_inputFieldClippingContainer addSubview:_inputField];
return _inputField;
}
- (HPGrowingTextView *)maybeInputField
{
return _inputField;
}
- (NSString *)text
{
return _inputField.text;
}
#pragma mark -
- (BOOL)becomeFirstResponder
{
if (!_inputField.isFirstResponder)
[TGAppDelegateInstance.window endEditing:true];
[self handleFieldBackgroundTap:nil];
return true;
}
- (BOOL)resignFirstResponder
{
[_inputField resignFirstResponder];
return true;
}
- (BOOL)isFirstResponder
{
return _inputField.internalTextView.isFirstResponder;
}
- (void)growingTextViewDidBeginEditing:(HPGrowingTextView *)__unused growingTextView
{
[_inputField refreshHeight:false];
[self updateModeButtonVisibilityForce:true];
}
-(void)growingTextViewDidEndEditing:(HPGrowingTextView *)__unused growingTextView
{
[self setAssociatedPanel:nil animated:true];
}
- (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);
id<TGNotificationReplyPanelDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelWillChangeHeight:height:duration:animationCurve:)])
[delegate inputPanelWillChangeHeight:self height:inputContainerHeight duration:duration animationCurve:animationCurve];
}
- (void)growingTextViewDidChange:(HPGrowingTextView *)__unused growingTextView afterSetText:(bool)__unused afterSetText afterPastingText:(bool)__unused afterPastingText
{
id<TGNotificationReplyPanelDelegate> delegate = self.delegate;
int textLength = (int)growingTextView.text.length;
NSString *text = growingTextView.text;
if (!_notIdle && textLength > 0)
_notIdle = true;
UITextRange *selRange = _inputField.internalTextView.selectedTextRange;
UITextPosition *selStartPos = selRange.start;
NSInteger idx = [_inputField.internalTextView offsetFromPosition:_inputField.internalTextView.beginningOfDocument toPosition:selStartPos];
idx--;
NSString *candidateMention = nil;
NSString *candidateHashtag = nil;
if (idx >= 0 && idx < textLength)
{
for (NSInteger i = idx; i >= 0; i--)
{
unichar c = [text characterAtIndex:i];
if (c == '@')
{
if (i == idx)
candidateMention = @"";
else
{
@try {
candidateMention = [text substringWithRange:NSMakeRange(i + 1, idx - i)];
} @catch(NSException *e) { }
}
break;
}
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'))
break;
}
}
if (candidateMention == 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];
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 ([delegate respondsToSelector:@selector(inputPanelMentionEntered:mention:startOfLine:)])
[delegate inputPanelMentionEntered:self mention:candidateMention startOfLine:false];
if ([delegate respondsToSelector:@selector(inputPanelHashtagEntered:hashtag:)])
[delegate inputPanelHashtagEntered:self hashtag:candidateHashtag];
if ([delegate respondsToSelector:@selector(inputPanelTextChanged:text:)])
[delegate inputPanelTextChanged:self text:text];
[self updateSendButtonVisibility];
[self updateModeButtonVisibilityForce:false];
[self updateStickerPanel];
}
- (void)growingTextView:(HPGrowingTextView *)__unused growingTextView receivedReturnKeyCommandWithModifierFlags:(UIKeyModifierFlags)flags
{
if (flags & UIKeyModifierAlternate)
[self addNewLine];
else
[self sendButtonPressed];
}
- (void)addNewLine
{
_inputField.text = [NSString stringWithFormat:@"%@\n", _inputField.text];
}
#pragma mark -
- (void)handleFieldBackgroundTap:(UITapGestureRecognizer *)__unused gestureRecognizer
{
[self.inputField becomeFirstResponder];
}
- (void)sendButtonPressed
{
id<TGNotificationReplyPanelDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelRequestedSendText:text:)])
[delegate inputPanelRequestedSendText:self text:_inputField.text];
}
- (void)keyboardModeButtonPressed
{
if (self.maybeInputField.internalTextView.inputView == nil)
return;
if (self.maybeInputField.isFirstResponder)
{
_changingKeyboardMode = true;
[self.inputField resignFirstResponder];
}
self.inputField.internalTextView.inputView = nil;
self.inputField.internalTextView.enableFirstResponder = true;
[self.inputField becomeFirstResponder];
_changingKeyboardMode = false;
[self updateModeButtonVisibilityForce:false];
}
- (void)stickerModeButtonPressed
{
_notIdle = true;
if ([self.inputField.internalTextView.inputView isKindOfClass:[TGStickerKeyboardView class]])
return;
if (self.maybeInputField.isFirstResponder)
{
_changingKeyboardMode = true;
[self.inputField resignFirstResponder];
}
if (_stickerKeyboardView == nil)
{
_stickerKeyboardView = [[TGStickerKeyboardView alloc] initWithFrame:CGRectZero style:TGStickerKeyboardViewDarkBlurredStyle];
id<TGNotificationReplyPanelDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelParentViewController:)])
_stickerKeyboardView.parentViewController = [delegate inputPanelParentViewController:self];
__weak TGNotificationReplyPanelView *weakSelf = self;
_stickerKeyboardView.stickerSelected = ^(TGDocumentMediaAttachment *sticker)
{
__strong TGNotificationReplyPanelView *strongSelf = weakSelf;
if (strongSelf != nil)
{
id<TGNotificationReplyPanelDelegate> delegate = strongSelf.delegate;
[delegate inputPanelRequestedSendSticker:strongSelf sticker:sticker];
}
};
[_stickerKeyboardView sizeToFitForWidth:self.frame.size.width];
}
else
{
[_stickerKeyboardView updateIfNeeded];
}
self.inputField.internalTextView.inputView = _stickerKeyboardView;
self.inputField.internalTextView.enableFirstResponder = true;
[self.inputField becomeFirstResponder];
_changingKeyboardMode = false;
[self updateModeButtonVisibilityForce:false];
}
- (void)updateSendButtonVisibility
{
_sendButton.enabled = [self.inputField.text hasNonWhitespaceCharacters];
}
- (void)updateModeButtonVisibilityForce:(bool)force
{
NSMutableArray *commands = [[NSMutableArray alloc] init];
if (self.maybeInputField.internalTextView.inputView != nil)
{
[commands addObject:_keyboardModeButton];
}
else
{
if (_inputField.text.length == 0 && !(TGAppDelegateInstance.alwaysShowStickersMode != 2))
[commands addObject:_stickerModeButton];
}
[self setModeButtons:commands force:force];
}
- (void)setModeButtons:(NSArray *)modeButtons force:(bool)force
{
if (!force && [_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)
{
[_wrapperView 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];
}
#pragma mark -
- (void)replaceMention:(NSString *)mention
{
[TGModernConversationInputTextPanel replaceMention:mention inputField:_inputField];
}
- (void)replaceHashtag:(NSString *)hashtag
{
[TGModernConversationInputTextPanel replaceHashtag:hashtag inputField:_inputField];
}
- (bool)shouldDisplayPanels
{
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone && UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation))
return false;
return true;
}
- (TGModernConversationAssociatedInputPanel *)associatedPanel
{
return _associatedPanel;
}
- (void)setAssociatedPanel:(TGModernConversationAssociatedInputPanel *)associatedPanel animated:(bool)animated
{
int screenSize = (int)TGScreenSize().height;
if (screenSize < 568)
return;
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 TGNotificationReplyPanelView *weakSelf = self;
_associatedPanel.preferredHeightUpdated = ^
{
__strong TGNotificationReplyPanelView *strongSelf = weakSelf;
if (strongSelf != nil)
{
strongSelf->_associatedPanel.frame = CGRectMake(0.0f, strongSelf.frame.size.height, strongSelf.frame.size.width, [strongSelf shouldDisplayPanels] ? [strongSelf->_associatedPanel preferredHeight] : 0.0f);
}
};
_associatedPanel.frame = CGRectMake(0.0f, self.frame.size.height, self.frame.size.width, [self shouldDisplayPanels] ? [_associatedPanel preferredHeight] : 0.0f);
[self addSubview:_associatedPanel];
if (animated)
{
_associatedPanel.alpha = 0.0f;
[UIView animateWithDuration:0.18 animations:^
{
_associatedPanel.alpha = 1.0f;
}];
}
else
{
_associatedPanel.alpha = 1.0f;
}
}
}
}
- (void)setAssociatedStickerList:(NSArray *)stickerList
{
int screenSize = (int)TGScreenSize().height;
if (screenSize < 568)
return;
if (stickerList.count != 0)
{
if ([_associatedPanel isKindOfClass:[TGStickerAssociatedInputPanel class]])
[((TGStickerAssociatedInputPanel *)_associatedPanel) setDocumentList:stickerList];
else
{
__weak TGNotificationReplyPanelView *weakSelf = self;
TGStickerAssociatedInputPanel *stickerPanel = [[TGStickerAssociatedInputPanel alloc] initWithStyle:TGModernConversationAssociatedInputPanelDarkBlurredStyle];
stickerPanel.documentSelected = ^(TGDocumentMediaAttachment *sticker)
{
__strong TGNotificationReplyPanelView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if ([strongSelf.delegate respondsToSelector:@selector(inputPanelRequestedSendSticker:sticker:)])
[strongSelf.delegate inputPanelRequestedSendSticker:strongSelf sticker:sticker];
[strongSelf->_inputField setText:@"" animated:true];
};
[stickerPanel setDocumentList:stickerList];
[stickerPanel setTargetOffset:53.0f];
[self setAssociatedPanel:stickerPanel animated:true];
}
}
else
{
[self setAssociatedPanel:nil animated:true];
}
}
- (void)updateStickerPanel
{
}
#pragma mark -
- (bool)hasUnsavedData
{
return (self.maybeInputField.text.length > 0);
}
- (bool)isIdle
{
return !_notIdle && (self.maybeInputField.text.length == 0);
}
- (void)refreshHeight
{
[_inputField refreshHeight:false];
}
- (void)localizationUpdated
{
_sendButtonWidth = MIN(100.0f, [TGLocalized(@"Conversation.Send") sizeWithFont:TGMediumSystemFontOfSize(17)].width + 8.0f);
[_sendButton setTitle:TGLocalized(@"Conversation.Send") forState:UIControlStateNormal];
[self setNeedsLayout];
}
- (void)reset
{
_inputField.internalTextView.inputView = nil;
_inputField.text = nil;
_stickerKeyboardView = nil;
_notIdle = false;
}
#pragma mark -
- (CGFloat)_baseHeight
{
return 46;
}
- (UIEdgeInsets)_inputFieldInsets
{
static UIEdgeInsets insets;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
insets = UIEdgeInsetsMake(9.0f, 9.0f, 9.0f, 0.0f);
});
return insets;
}
- (UIEdgeInsets)_inputFieldInternalEdgeInsets
{
static UIEdgeInsets insets;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
insets = UIEdgeInsetsMake(-4 - TGRetinaPixel, 1.0f, 0.0f, 0.0f);
});
return insets;
}
- (CGFloat)heightForWidth:(CGFloat)__unused width
{
UIEdgeInsets inputFieldInsets = [self _inputFieldInsets];
CGFloat height = MAX([self _baseHeight], _inputField.frame.size.height - 8 + inputFieldInsets.top + inputFieldInsets.bottom);
return height;
}
- (CGFloat)heightForInputFieldHeight:(CGFloat)inputFieldHeight
{
if (inputFieldHeight < FLT_EPSILON)
inputFieldHeight = 36;
UIEdgeInsets inputFieldInsets = [self _inputFieldInsets];
CGFloat height = MAX([self _baseHeight], inputFieldHeight - 8 + inputFieldInsets.top + inputFieldInsets.bottom);
return height;
}
- (UIEdgeInsets)edgeInsets
{
return UIEdgeInsetsMake(9, 9, 9, 50);
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (_associatedPanel != nil)
{
UIView *result = [_associatedPanel hitTest:[self convertPoint:point toView:_associatedPanel] withEvent:event];
if (result != nil)
return result;
}
return [super hitTest:point withEvent:event];
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
bool pointInside = [super pointInside:point withEvent:event];
if (_associatedPanel == nil)
return pointInside;
return CGRectContainsPoint(_associatedPanel.frame, point) || pointInside;
}
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;
}
- (void)layoutSubviews
{
CGRect frame = self.frame;
if (_associatedPanel != nil)
{
CGRect associatedPanelFrame = CGRectMake(0.0f, self.frame.size.height, frame.size.width, [self shouldDisplayPanels] ? [_associatedPanel preferredHeight] : 0.0f);
if (!CGRectEqualToRect(associatedPanelFrame, _associatedPanel.frame))
_associatedPanel.frame = associatedPanelFrame;
}
_separatorView.frame = CGRectMake(0, 0, frame.size.width, _separatorView.frame.size.height);
UIEdgeInsets inputFieldInsets = [self _inputFieldInsets];
CGFloat inputContainerHeight = [self heightForInputFieldHeight:self.isFirstResponder ? _inputField.frame.size.height : 0];
setViewFrame(_fieldBackground, CGRectMake(inputFieldInsets.left, inputFieldInsets.top, frame.size.width - inputFieldInsets.left - inputFieldInsets.right - _sendButtonWidth, inputContainerHeight - inputFieldInsets.top - inputFieldInsets.bottom));
UIEdgeInsets inputFieldInternalEdgeInsets = [self _inputFieldInternalEdgeInsets];
CGRect inputFieldClippingFrame = _fieldBackground.frame;
setViewFrame(_inputFieldClippingContainer, inputFieldClippingFrame);
CGFloat inputFieldWidth = _inputFieldClippingContainer.frame.size.width - inputFieldInternalEdgeInsets.left - inputFieldInternalEdgeInsets.right;
if (fabs(inputFieldWidth - _inputField.frame.size.width) > FLT_EPSILON)
{
CGRect inputFieldFrame = CGRectMake(inputFieldInternalEdgeInsets.left, inputFieldInternalEdgeInsets.top + TGRetinaPixel, inputFieldWidth, _inputFieldClippingContainer.frame.size.height);
setViewFrame(_inputField, inputFieldFrame);
}
_sendButton.frame = CGRectMake(frame.size.width - _sendButton.frame.size.width, frame.size.height - _sendButton.frame.size.height, _sendButtonWidth, _sendButton.frame.size.height);
_stickerModeButton.frame = CGRectMake(CGRectGetMaxX(_fieldBackground.frame) - _stickerModeButton.frame.size.width - 4.0f, CGRectGetMinY(_fieldBackground.frame), _stickerModeButton.frame.size.width, _stickerModeButton.frame.size.height);
_keyboardModeButton.frame = CGRectMake(CGRectGetMaxX(_fieldBackground.frame) - _keyboardModeButton.frame.size.width - 4.0f, CGRectGetMinY(_fieldBackground.frame), _keyboardModeButton.frame.size.width, _keyboardModeButton.frame.size.height);
}
@end