Swiftgram/submodules/LegacyComponents/Sources/TGMediaPickerCaptionInputPanel.m
Ilya Laktyushin afdd1462da Various Fixes
2021-03-14 18:08:43 +04:00

1066 lines
39 KiB
Objective-C

#import "TGMediaPickerCaptionInputPanel.h"
#import "TGMessage.h"
#import "TGInputTextTag.h"
#import "LegacyComponentsInternal.h"
#import "TGImageUtils.h"
#import "TGFont.h"
#import "TGViewController.h"
#import "TGHacks.h"
#import "TGModernButton.h"
#import "TGPhotoEditorInterfaceAssets.h"
#import "TGMediaAssetsController.h"
#import "HPTextViewInternal.h"
#import "TGModernConversationAssociatedInputPanel.h"
const NSInteger TGMediaPickerCaptionInputPanelCaptionLimit = 1024;
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;
}
@interface TGMediaPickerCaptionInputPanel () <HPGrowingTextViewDelegate>
{
CGFloat _keyboardHeight;
NSString *_caption;
bool _dismissing;
bool _dismissDisabled;
NSArray *_entities;
UIView *_wrapperView;
UIView *_backgroundView;
UIImageView *_fieldBackground;
UIView *_inputFieldClippingContainer;
HPGrowingTextView *_inputField;
UILabel *_placeholderLabel;
UIView *_doneButtonWrapper;
TGModernButton *_doneButton;
UILabel *_inputFieldOnelineLabel;
UILabel *_counterLabel;
TGModernConversationAssociatedInputPanel *_associatedPanel;
CGFloat _contentAreaHeight;
__weak TGKeyCommandController *_keyCommandController;
}
@end
@implementation TGMediaPickerCaptionInputPanel
- (instancetype)initWithKeyCommandController:(TGKeyCommandController *)keyCommandController frame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
_keyCommandController = keyCommandController;
static UIImage *fieldBackgroundImage = nil;
static UIImage *placeholderImage = nil;
static NSString *localizationPlaceholderText = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(33, 33), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGBA(0xffffff, 0.1f).CGColor);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 33, 33) cornerRadius:16.5f];
[path fill];
fieldBackgroundImage = [UIGraphicsGetImageFromCurrentImageContext() resizableImageWithCapInsets:UIEdgeInsetsMake(16, 16, 16, 16)];
UIGraphicsEndImageContext();
});
if (placeholderImage == nil || localizationPlaceholderText != TGLocalized(@"MediaPicker.AddCaption"))
{
localizationPlaceholderText = TGLocalized(@"MediaPicker.AddCaption");
NSString *placeholderText = TGLocalized(@"MediaPicker.AddCaption");
UIFont *placeholderFont = TGSystemFontOfSize(17);
CGSize placeholderSize = [placeholderText sizeWithFont:placeholderFont];
placeholderSize.width += 2.0f;
placeholderSize.height += 2.0f;
UIGraphicsBeginImageContextWithOptions(placeholderSize, false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGB(0xffffff).CGColor);
[placeholderText drawAtPoint:CGPointMake(1.0f, 1.0f) withFont:placeholderFont];
placeholderImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
UIView *backgroundWrapperView = [[UIView alloc] initWithFrame:frame];
backgroundWrapperView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
backgroundWrapperView.clipsToBounds = true;
[self addSubview:backgroundWrapperView];
_wrapperView = [[UIView alloc] initWithFrame:frame];
_wrapperView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:_wrapperView];
_backgroundView = [[UIView alloc] initWithFrame:_wrapperView.bounds];
_backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_backgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarTransparentBackgroundColor];
[backgroundWrapperView addSubview:_backgroundView];
_fieldBackground = [[UIImageView alloc] initWithImage:fieldBackgroundImage];
_fieldBackground.alpha = 0.0f;
_fieldBackground.userInteractionEnabled = true;
[_wrapperView addSubview:_fieldBackground];
_placeholderLabel = [[UILabel alloc] init];
_placeholderLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
_placeholderLabel.backgroundColor = [UIColor clearColor];
_placeholderLabel.font = TGSystemFontOfSize(17);
_placeholderLabel.textColor = UIColorRGB(0x7f7f7f);
_placeholderLabel.text = TGLocalized(@"MediaPicker.AddCaption");
_placeholderLabel.userInteractionEnabled = true;
[_placeholderLabel sizeToFit];
[_wrapperView addSubview:_placeholderLabel];
_inputFieldOnelineLabel = [[UILabel alloc] init];
_inputFieldOnelineLabel.backgroundColor = [UIColor clearColor];
_inputFieldOnelineLabel.font = TGSystemFontOfSize(17);
_inputFieldOnelineLabel.hidden = true;
_inputFieldOnelineLabel.numberOfLines = 1;
_inputFieldOnelineLabel.textColor = [UIColor whiteColor];
_inputFieldOnelineLabel.userInteractionEnabled = false;
[_wrapperView addSubview:_inputFieldOnelineLabel];
_counterLabel = [[UILabel alloc] initWithFrame:CGRectMake(_fieldBackground.frame.size.width - 45, 5, 36, 16)];
_counterLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
_counterLabel.backgroundColor = [UIColor clearColor];
_counterLabel.font = TGSystemFontOfSize(12);
_counterLabel.hidden = true;
_counterLabel.textAlignment = NSTextAlignmentRight;
_counterLabel.textColor = UIColorRGB(0x828282);
_counterLabel.highlightedTextColor = UIColorRGB(0xff4848);
_counterLabel.userInteractionEnabled = false;
[_fieldBackground addSubview:_counterLabel];
_doneButtonWrapper = [[UIView alloc] init];
_doneButtonWrapper.alpha = 0.0f;
_doneButtonWrapper.userInteractionEnabled = false;
[_wrapperView addSubview:_doneButtonWrapper];
CGSize buttonSize = CGSizeMake(49.0f, 49.0f);
_doneButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, buttonSize.width, buttonSize.height)];
_doneButton.exclusiveTouch = true;
_doneButton.adjustsImageWhenHighlighted = false;
[_doneButton addTarget:self action:@selector(setButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_doneButtonWrapper addSubview:_doneButton];
TGMediaAssetsPallete *pallete = nil;
if ([[LegacyComponentsGlobals provider] respondsToSelector:@selector(mediaAssetsPallete)])
pallete = [[LegacyComponentsGlobals provider] mediaAssetsPallete];
UIImage *doneImage = pallete != nil ? pallete.doneIconImage : TGTintedImage([UIImage imageNamed:@"Editor/Commit"], [UIColor whiteColor]);
[_doneButton setImage:doneImage forState:UIControlStateNormal];
[_wrapperView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleFieldBackgroundTap:)]];
}
return self;
}
- (void)createInputFieldIfNeeded
{
if (_inputField != nil)
return;
CGRect inputFieldClippingFrame = _fieldBackground.frame;
_inputFieldClippingContainer = [[UIView alloc] initWithFrame:inputFieldClippingFrame];
_inputFieldClippingContainer.clipsToBounds = true;
[_wrapperView addSubview:_inputFieldClippingContainer];
UIEdgeInsets inputFieldInternalEdgeInsets = [self _inputFieldInternalEdgeInsets];
_inputField = [[HPGrowingTextView alloc] initWithKeyCommandController:_keyCommandController];
_inputField.frame = CGRectMake(inputFieldInternalEdgeInsets.left, inputFieldInternalEdgeInsets.top + TGRetinaPixel, _inputFieldClippingContainer.frame.size.width - inputFieldInternalEdgeInsets.left - 36, _inputFieldClippingContainer.frame.size.height);
_inputField.textColor = [UIColor whiteColor];
_inputField.disableFormatting = !_allowEntities;
_inputField.placeholderView = _placeholderLabel;
_inputField.font = TGSystemFontOfSize(17);
_inputField.accentColor = UIColorRGB(0x78b1f9);
_inputField.clipsToBounds = true;
_inputField.backgroundColor = nil;
_inputField.opaque = false;
_inputField.showPlaceholderWhenFocussed = true;
_inputField.internalTextView.returnKeyType = UIReturnKeyDefault;
_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 = [self _maxNumberOfLinesForSize:CGSizeMake(320.0f, 480.0f)];
_inputField.delegate = self;
_inputField.internalTextView.scrollIndicatorInsets = UIEdgeInsetsMake(-inputFieldInternalEdgeInsets.top, 0, 5 - TGRetinaPixel, 0);
[_inputField setAttributedText:[TGMediaPickerCaptionInputPanel attributedStringForText:_caption entities:_entities fontSize:17.0f] keepFormatting:true animated:false];
[_inputFieldClippingContainer addSubview:_inputField];
}
- (void)setAllowEntities:(bool)allowEntities
{
_allowEntities = allowEntities;
_inputField.disableFormatting = !_allowEntities;
}
- (void)handleFieldBackgroundTap:(UITapGestureRecognizer *)__unused gestureRecognizer
{
bool shouldBecomeFirstResponder = true;
id<TGMediaPickerCaptionInputPanelDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelShouldBecomeFirstResponder:)])
shouldBecomeFirstResponder = [delegate inputPanelShouldBecomeFirstResponder:self];
if (!shouldBecomeFirstResponder || self.isCollapsed)
return;
[self createInputFieldIfNeeded];
_inputFieldClippingContainer.hidden = false;
_inputField.internalTextView.enableFirstResponder = true;
[_inputField.internalTextView becomeFirstResponder];
}
- (bool)setButtonPressed
{
if (_dismissDisabled)
return false;
if (_inputField.text.length > TGMediaPickerCaptionInputPanelCaptionLimit)
{
[self shakeControls];
return false;
}
if (_inputField.internalTextView.isFirstResponder)
[TGHacks applyCurrentKeyboardAutocorrectionVariant];
NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithAttributedString:_inputField.text == nil ? [[NSAttributedString alloc] initWithString:@""] : _inputField.attributedText];
NSMutableString *usualString = [text.string mutableCopy];
int textLength = (int)text.length;
for (int i = 0; i < textLength; i++)
{
unichar c = [usualString characterAtIndex:i];
if (c == ' ' || c == '\t' || c == '\n')
{
[text deleteCharactersInRange:NSMakeRange(i, 1)];
[usualString deleteCharactersInRange:NSMakeRange(i, 1)];
i--;
textLength--;
}
else
break;
}
_inputField.internalTextView.attributedText = text;
__autoreleasing NSArray *entities = nil;
NSString *finalText = [_inputField textWithEntities:&entities];
id<TGMediaPickerCaptionInputPanelDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelRequestedSetCaption:text:entities:)])
[delegate inputPanelRequestedSetCaption:self text:finalText entities:entities];
_dismissing = true;
[_inputField.internalTextView resignFirstResponder];
return true;
}
- (void)dismiss
{
[self setButtonPressed];
}
#pragma mark -
- (void)setCollapsed:(bool)collapsed
{
[self setCollapsed:collapsed animated:false];
}
- (void)setCollapsed:(bool)collapsed animated:(bool)animated
{
_collapsed = collapsed;
void (^frameChangeBlock)(void) = ^
{
_backgroundView.frame = CGRectMake(_backgroundView.frame.origin.x,
collapsed ? self.frame.size.height : 0,
_backgroundView.frame.size.width, _backgroundView.frame.size.height);
_wrapperView.frame = CGRectMake(_wrapperView.frame.origin.x,
collapsed ? self.frame.size.height : 0,
_wrapperView.frame.size.width, _wrapperView.frame.size.height);
};
void (^visibilityChangeBlock)(void) = ^
{
CGFloat alpha = collapsed ? 0.0f : 1.0f;
_wrapperView.alpha = alpha;
};
if (animated)
{
[UIView animateWithDuration:0.3f delay:0.0f options:[TGViewController preferredAnimationCurve] << 16 animations:frameChangeBlock completion:nil];
[UIView animateWithDuration:0.25f delay:collapsed ? 0.0f : 0.05f options:kNilOptions animations:visibilityChangeBlock completion:nil];
}
else
{
frameChangeBlock();
visibilityChangeBlock();
}
}
#pragma mark -
- (void)adjustForOrientation:(UIInterfaceOrientation)orientation keyboardHeight:(CGFloat)keyboardHeight duration:(NSTimeInterval)duration animationCurve:(NSInteger)animationCurve
{
[self adjustForOrientation:orientation keyboardHeight:keyboardHeight duration:duration animationCurve:animationCurve completion:nil];
}
- (void)adjustForOrientation:(UIInterfaceOrientation)__unused orientation keyboardHeight:(CGFloat)keyboardHeight duration:(NSTimeInterval)duration animationCurve:(NSInteger)animationCurve completion:(void (^)(void))completion
{
_keyboardHeight = keyboardHeight;
void(^changeBlock)(void) = ^
{
bool isKeyboardVisible = (keyboardHeight > FLT_EPSILON);
CGFloat inputContainerHeight = [self heightForInputFieldHeight:[self isFirstResponder] ? _inputField.frame.size.height : 0];
CGSize screenSize = self.superview.frame.size;
if (isKeyboardVisible)
{
self.frame = CGRectMake(self.frame.origin.x, screenSize.height - keyboardHeight - inputContainerHeight, self.frame.size.width, inputContainerHeight + keyboardHeight - self.bottomMargin);
_backgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarBackgroundColor];
}
else
{
self.frame = CGRectMake(self.frame.origin.x, screenSize.height - self.bottomMargin - inputContainerHeight, self.frame.size.width, inputContainerHeight);
_backgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarTransparentBackgroundColor];
}
[self layoutSubviews];
};
void (^finishedBlock)(BOOL) = ^(__unused BOOL finished)
{
if (completion != nil)
completion();
};
if (duration > DBL_EPSILON)
{
[UIView animateWithDuration:duration delay:0.0f options:animationCurve animations:changeBlock completion:finishedBlock];
}
else
{
changeBlock();
finishedBlock(true);
}
}
#pragma mark -
- (NSString *)caption
{
return _caption;
}
- (void)setCaption:(NSString *)caption
{
[self setCaption:caption entities:nil animated:false];
}
- (void)setCaption:(NSString *)caption entities:(NSArray *)entities animated:(bool)animated
{
NSString *previousCaption = _caption;
_caption = caption;
_entities = entities;
if (animated)
{
_inputFieldOnelineLabel.attributedText = [self oneLinedCaptionForText:caption entities:entities];
if ([previousCaption isEqualToString:caption] || (previousCaption.length == 0 && caption.length == 0))
return;
UIView *snapshotView = nil;
UIView *snapshottedView = nil;
UIView *fadingInView = nil;
if (previousCaption.length > 0)
snapshottedView = _inputFieldOnelineLabel;
else
snapshottedView = _placeholderLabel;
snapshotView = [snapshottedView snapshotViewAfterScreenUpdates:false];
snapshotView.frame = snapshottedView.frame;
[snapshottedView.superview addSubview:snapshotView];
if (previousCaption.length > 0 && caption.length == 0)
fadingInView = _placeholderLabel;
else
fadingInView = _inputFieldOnelineLabel;
fadingInView.hidden = false;
fadingInView.alpha = 0.0f;
_placeholderLabel.hidden = (caption.length > 0);
[UIView animateWithDuration:0.3f delay:0.05f options:UIViewAnimationOptionCurveEaseInOut animations:^
{
fadingInView.alpha = 1.0f;
} completion:nil];
[UIView animateWithDuration:0.21f delay:0.0f options:UIViewAnimationOptionCurveEaseOut animations:^
{
snapshotView.alpha = 0.0f;
_fieldBackground.alpha = _placeholderLabel.hidden ? 1.0f : 0.0f;
} completion:^(__unused BOOL finished)
{
[snapshotView removeFromSuperview];
}];
}
else
{
_inputFieldOnelineLabel.attributedText = [self oneLinedCaptionForText:caption entities:entities];
_inputFieldOnelineLabel.hidden = (caption.length == 0);
_placeholderLabel.hidden = !_inputFieldOnelineLabel.hidden;
_fieldBackground.alpha = _placeholderLabel.hidden ? 1.0f : 0.0f;
}
[self.inputField setAttributedText:[TGMediaPickerCaptionInputPanel attributedStringForText:_caption entities:_entities fontSize:17.0f] keepFormatting:true animated:false];
}
+ (NSAttributedString *)attributedStringForText:(NSString *)text entities:(NSArray *)entities fontSize:(CGFloat)fontSize {
if (text == nil) {
return nil;
}
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName: TGSystemFontOfSize(fontSize)}];
for (id entity in entities) {
if ([entity isKindOfClass:[TGMessageEntityMentionName class]]) {
TGMessageEntityMentionName *mentionEntity = entity;
static int64_t nextId = 1000000;
int64_t uniqueId = nextId;
nextId++;
@try {
[attributedString addAttributes:@{TGMentionUidAttributeName: [[TGInputTextTag alloc] initWithUniqueId:uniqueId left:true attachment:@(mentionEntity.userId)]} range:mentionEntity.range];
} @catch(NSException *e) {
}
}
else if (iosMajorVersion() >= 7) {
if ([entity isKindOfClass:[TGMessageEntityBold class]]) {
TGMessageEntityBold *boldEntity = entity;
@try {
[attributedString addAttributes:@{NSFontAttributeName: TGBoldSystemFontOfSize(fontSize)} range:boldEntity.range];
} @catch(NSException *e) {
}
} else if ([entity isKindOfClass:[TGMessageEntityItalic class]]) {
TGMessageEntityItalic *italicEntity = entity;
@try {
[attributedString addAttributes:@{NSFontAttributeName: TGItalicSystemFontOfSize(fontSize)} range:italicEntity.range];
} @catch(NSException *e) {
}
}
}
}
return attributedString;
}
- (void)updateCounterWithText:(NSString *)text
{
bool appearance = false;
NSInteger textLength = text.length;
_counterLabel.text = [NSString stringWithFormat:@"%d", (int)(TGMediaPickerCaptionInputPanelCaptionLimit - textLength)];
bool hidden = (text.length < (TGMediaPickerCaptionInputPanelCaptionLimit - 100));
if (hidden != _counterLabel.hidden)
{
appearance = true;
[UIView transitionWithView:_counterLabel duration:0.16f options:UIViewAnimationOptionTransitionCrossDissolve animations:^
{
_counterLabel.hidden = hidden;
} completion:nil];
}
bool highlighted = (textLength > TGMediaPickerCaptionInputPanelCaptionLimit);
if (highlighted != _counterLabel.highlighted)
{
if (!appearance)
{
[UIView transitionWithView:_counterLabel duration:0.16f options:UIViewAnimationOptionTransitionCrossDissolve animations:^
{
_counterLabel.highlighted = highlighted;
} completion:nil];
}
else
{
_counterLabel.highlighted = highlighted;
}
}
_counterLabel.hidden = ![self isFirstResponder] || textLength < (TGMediaPickerCaptionInputPanelCaptionLimit - 100);
}
- (void)shakeControls
{
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
NSMutableArray *values = [[NSMutableArray alloc] init];
for (NSUInteger i = 0; i < 6; 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.3;
[_wrapperView.layer addAnimation:animation forKey:@"transform"];
_dismissDisabled = true;
TGDispatchAfter(0.3, dispatch_get_main_queue(), ^
{
_dismissDisabled = false;
});
}
#pragma mark -
- (BOOL)becomeFirstResponder
{
[self handleFieldBackgroundTap:nil];
return true;
}
- (BOOL)isFirstResponder
{
return _inputField.internalTextView.isFirstResponder && !_dismissing;
}
- (void)growingTextViewDidBeginEditing:(HPGrowingTextView *)__unused growingTextView
{
id<TGMediaPickerCaptionInputPanelDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(inputPanelFocused:)])
[delegate inputPanelFocused:self];
[self updateCounterWithText:_caption];
_inputField.alpha = 0.0f;
_doneButtonWrapper.userInteractionEnabled = true;
[UIView animateWithDuration:0.2f animations:^
{
_inputField.alpha = 1.0f;
_inputFieldOnelineLabel.alpha = 0.0f;
_fieldBackground.alpha = 1.0f;
_doneButtonWrapper.alpha = 1.0;
} completion:^(BOOL finished)
{
if (finished)
{
_inputFieldOnelineLabel.alpha = 1.0f;
_inputFieldOnelineLabel.hidden = true;
}
}];
if (_keyboardHeight < FLT_EPSILON)
{
[self adjustForOrientation:UIInterfaceOrientationPortrait keyboardHeight:0 duration:0.2f animationCurve:[TGViewController preferredAnimationCurve]];
}
[_inputField refreshHeight:false];
}
- (void)growingTextViewDidEndEditing:(HPGrowingTextView *)__unused growingTextView
{
_caption = _inputField.text;
__autoreleasing NSArray *entities = nil;
[_inputField textWithEntities:&entities];
_entities = entities;
_inputFieldOnelineLabel.attributedText = [self oneLinedCaptionForText:_caption entities:_entities];
_inputFieldOnelineLabel.alpha = 0.0f;
_inputFieldOnelineLabel.hidden = false;
[self updateCounterWithText:_caption];
_doneButtonWrapper.userInteractionEnabled = false;
[UIView animateWithDuration:0.2f animations:^
{
_inputField.alpha = 0.0f;
_inputFieldOnelineLabel.alpha = 1.0f;
_doneButtonWrapper.alpha = 0.0;
if (_caption.length == 0)
_fieldBackground.alpha = 0.0f;
} completion:^(BOOL finished)
{
if (finished)
{
_inputField.alpha = 1.0f;
_inputFieldClippingContainer.hidden = true;
}
}];
[self setAssociatedPanel:nil animated:true];
[self setButtonPressed];
}
- (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<TGMediaPickerCaptionInputPanelDelegate> delegate = (id<TGMediaPickerCaptionInputPanelDelegate>)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<TGMediaPickerCaptionInputPanelDelegate> delegate = (id<TGMediaPickerCaptionInputPanelDelegate>)self.delegate;
int textLength = (int)growingTextView.text.length;
NSString *text = growingTextView.text;
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 *candidateHashtag = nil;
NSString *candidateAlphacode = nil;
if (idx >= 0 && idx < textLength)
{
for (NSInteger i = idx; i >= 0; i--)
{
unichar c = [text characterAtIndex:i];
if (c == '@')
{
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)
{
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 (candidateHashtag == nil)
{
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) {
candidateAlphacode = nil;
}
else
{
@try {
candidateAlphacode = [text substringWithRange:NSMakeRange(i + 1, idx - i)];
} @catch(NSException *e) { }
}
break;
}
if (c == ' ' || (![characterSet characterIsMember:c]))
break;
}
}
}
}
if ([delegate respondsToSelector:@selector(inputPanelMentionEntered:mention:startOfLine:)])
[delegate inputPanelMentionEntered:self mention:candidateMention startOfLine:candidateMentionStartOfLine];
if ([delegate respondsToSelector:@selector(inputPanelHashtagEntered:hashtag:)])
[delegate inputPanelHashtagEntered:self hashtag:candidateHashtag];
if ([delegate respondsToSelector:@selector(inputPanelAlphacodeEntered:alphacode:)])
[delegate inputPanelAlphacodeEntered:self alphacode:candidateAlphacode];
if ([delegate respondsToSelector:@selector(inputPanelTextChanged:text:)])
[delegate inputPanelTextChanged:self text:text];
[self updateCounterWithText:text];
}
- (void)addNewLine
{
self.caption = [NSString stringWithFormat:@"%@\n", self.caption];
}
- (NSMutableAttributedString *)oneLinedCaptionForText:(NSString *)text entities:(NSArray *)entities
{
static NSString *tokenString = nil;
if (tokenString == nil)
{
unichar tokenChar = 0x2026;
tokenString = [[NSString alloc] initWithCharacters:&tokenChar length:1];
}
if (text == nil)
return nil;
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithAttributedString:[TGMediaPickerCaptionInputPanel attributedStringForText:text entities:entities fontSize:17.0f]];
for (NSUInteger i = 0; i < string.length; i++)
{
unichar c = [text characterAtIndex:i];
if (c == '\t' || c == '\n')
{
[string insertAttributedString:[[NSAttributedString alloc] initWithString:tokenString attributes:@{NSFontAttributeName:TGSystemFontOfSize(17.0f)}] atIndex:i];
break;
}
}
return string;
}
- (NSInteger)textCaretPosition {
UITextRange *selRange = _inputField.internalTextView.selectedTextRange;
UITextPosition *selStartPos = selRange.start;
NSInteger idx = [_inputField.internalTextView offsetFromPosition:_inputField.internalTextView.beginningOfDocument toPosition:selStartPos];
return idx;
}
#pragma mark -
- (void)replaceMention:(NSString *)mention
{
[HPGrowingTextView replaceMention:mention inputField:_inputField username:true userId:0];
}
- (void)replaceMention:(NSString *)mention username:(bool)username userId:(int32_t)userId {
[HPGrowingTextView replaceMention:mention inputField:_inputField username:username userId:userId];
}
- (void)replaceHashtag:(NSString *)hashtag
{
[HPGrowingTextView replaceHashtag:hashtag inputField:_inputField];
}
- (bool)shouldDisplayPanels
{
return true;
}
- (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)
{
if ([_associatedPanel fillsAvailableSpace]) {
CGFloat inputContainerHeight = [self heightForInputFieldHeight:[self isFirstResponder] ? _inputField.frame.size.height : 0];
_associatedPanel.frame = CGRectMake(0.0f, -_contentAreaHeight + inputContainerHeight, self.frame.size.width, _contentAreaHeight - inputContainerHeight);
} else {
__weak TGMediaPickerCaptionInputPanel *weakSelf = self;
_associatedPanel.preferredHeightUpdated = ^
{
__strong TGMediaPickerCaptionInputPanel *strongSelf = weakSelf;
if (strongSelf != nil)
{
strongSelf->_associatedPanel.frame = CGRectMake(0.0f, -[strongSelf->_associatedPanel preferredHeight], strongSelf.frame.size.width, [strongSelf shouldDisplayPanels] ? [strongSelf->_associatedPanel preferredHeight] : 0.0f);
}
};
_associatedPanel.frame = CGRectMake(0.0f, -[_associatedPanel preferredHeight], 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)setContentAreaHeight:(CGFloat)contentAreaHeight
{
_contentAreaHeight = contentAreaHeight;
[self setNeedsLayout];
_dismissing = false;
}
#pragma mark - Style
- (UIEdgeInsets)_inputFieldInsets
{
static UIEdgeInsets insets;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
insets = UIEdgeInsetsMake(6.0f, 6.0f, 6.0f, 6.0f);
else
insets = UIEdgeInsetsMake(11.0f, 11.0f, 11.0f, 11.0f);
});
return insets;
}
- (UIEdgeInsets)_inputFieldInternalEdgeInsets
{
static UIEdgeInsets insets;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
if (!TGIsPad())
insets = UIEdgeInsetsMake(-3.0f, 8.0f, 0.0f, 0.0f);
else
insets = UIEdgeInsetsMake(-2.0f, 8.0f, 0.0f, 0.0f);
});
return insets;
}
- (CGPoint)_inputFieldPlaceholderOffset
{
static CGPoint offset;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
if (!TGIsPad())
offset = CGPointMake(12.0f, 5.0f + TGScreenPixel);
else
offset = CGPointMake(12.0f, 6.0f);
});
return offset;
}
- (CGFloat)heightForInputFieldHeight:(CGFloat)inputFieldHeight
{
if (inputFieldHeight < FLT_EPSILON)
inputFieldHeight = 36;
if (TGIsPad())
inputFieldHeight += 4;
UIEdgeInsets inputFieldInsets = [self _inputFieldInsets];
CGFloat height = MAX([self baseHeight], inputFieldHeight - 4 + inputFieldInsets.top + inputFieldInsets.bottom);
return height;
}
- (CGFloat)baseHeight
{
static CGFloat value = 0.0f;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
value = !TGIsPad() ? 45.0f : 56.0f;
});
return value;
}
- (CGPoint)_setButtonOffset
{
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;
}
- (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;
}
}
#pragma mark -
- (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];
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGRect frame = self.frame;
if (_associatedPanel != nil)
{
CGFloat inputContainerHeight = [self heightForInputFieldHeight:[self isFirstResponder] ? _inputField.frame.size.height : 0];
CGRect associatedPanelFrame = CGRectZero;
if ([_associatedPanel fillsAvailableSpace]) {
associatedPanelFrame = CGRectMake(0.0f, -_contentAreaHeight + inputContainerHeight, self.frame.size.width, _contentAreaHeight - inputContainerHeight);
} else {
associatedPanelFrame = CGRectMake(0.0f, -[_associatedPanel preferredHeight], frame.size.width, [self shouldDisplayPanels] ? [_associatedPanel preferredHeight] : 0.0f);
}
if (!CGRectEqualToRect(associatedPanelFrame, _associatedPanel.frame))
_associatedPanel.frame = associatedPanelFrame;
}
UIEdgeInsets inputFieldInsets = [self _inputFieldInsets];
if (self.isFirstResponder) {
inputFieldInsets.right += 41.0;
}
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, inputContainerHeight - inputFieldInsets.top - inputFieldInsets.bottom));
UIEdgeInsets inputFieldInternalEdgeInsets = [self _inputFieldInternalEdgeInsets];
CGRect onelineFrame = _fieldBackground.frame;
onelineFrame.origin.x += inputFieldInternalEdgeInsets.left + 5;
onelineFrame.origin.y += inputFieldInternalEdgeInsets.top + TGScreenPixel;
onelineFrame.size.width -= inputFieldInternalEdgeInsets.left * 2 + 10;
onelineFrame.size.height = 36;
setViewFrame(_inputFieldOnelineLabel, onelineFrame);
CGRect placeholderFrame = CGRectMake(floor((self.frame.size.width - _placeholderLabel.frame.size.width) / 2.0f), floor(([self baseHeight] - _placeholderLabel.frame.size.height) / 2.0f), _placeholderLabel.frame.size.width, _placeholderLabel.frame.size.height);
if (self.isFirstResponder)
placeholderFrame.origin.x = onelineFrame.origin.x;
setViewFrame(_placeholderLabel, placeholderFrame);
CGRect inputFieldClippingFrame = _fieldBackground.frame;
setViewFrame(_inputFieldClippingContainer, inputFieldClippingFrame);
CGFloat inputFieldWidth = _inputFieldClippingContainer.frame.size.width - inputFieldInternalEdgeInsets.left - 36;
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);
}
_doneButtonWrapper.frame = CGRectMake(self.frame.size.width - 47.0, CGRectGetMaxY(_fieldBackground.frame) - _doneButton.frame.size.height + 8.0, _doneButton.frame.size.width, _doneButton.frame.size.height);
}
@end