diff --git a/Signal/src/ViewControllers/ConversationView/ConversationInputTextView.h b/Signal/src/ViewControllers/ConversationView/ConversationInputTextView.h index 38cf306e8..8ea7bd93b 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationInputTextView.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationInputTextView.h @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // NS_ASSUME_NONNULL_BEGIN @@ -18,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol ConversationTextViewToolbarDelegate -- (void)textViewDidChange; +- (void)textViewDidChange:(UITextView *)textView; @end diff --git a/Signal/src/ViewControllers/ConversationView/ConversationInputTextView.m b/Signal/src/ViewControllers/ConversationView/ConversationInputTextView.m index 86885e0eb..591c5d0d4 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationInputTextView.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationInputTextView.m @@ -27,20 +27,17 @@ NS_ASSUME_NONNULL_BEGIN self.delegate = self; - CGFloat cornerRadius = 6.0f; - - self.backgroundColor = [UIColor whiteColor]; - self.layer.borderColor = [UIColor lightGrayColor].CGColor; + self.backgroundColor = [UIColor ows_light02Color]; + self.layer.borderColor = [UIColor.ows_blackColor colorWithAlphaComponent:0.12f].CGColor; self.layer.borderWidth = 0.5f; - self.layer.cornerRadius = cornerRadius; - self.scrollIndicatorInsets = UIEdgeInsetsMake(cornerRadius, 0.0f, cornerRadius, 0.0f); + self.scrollIndicatorInsets = UIEdgeInsetsMake(4, 4, 4, 4); self.scrollEnabled = YES; self.scrollsToTop = NO; self.userInteractionEnabled = YES; - self.font = [UIFont systemFontOfSize:16.0f]; + self.font = [UIFont ows_dynamicTypeBodyFont]; self.textColor = [UIColor blackColor]; self.textAlignment = NSTextAlignmentNatural; @@ -51,14 +48,13 @@ NS_ASSUME_NONNULL_BEGIN self.placeholderView = [UILabel new]; self.placeholderView.text = NSLocalizedString(@"new_message", @""); - self.placeholderView.textColor = [UIColor lightGrayColor]; + self.placeholderView.textColor = UIColor.ows_light35Color; self.placeholderView.userInteractionEnabled = NO; [self addSubview:self.placeholderView]; // We need to do these steps _after_ placeholderView is configured. self.font = [UIFont ows_dynamicTypeBodyFont]; - self.textContainerInset = UIEdgeInsetsMake(4.0f, 2.0f, 4.0f, 2.0f); - self.contentInset = UIEdgeInsetsMake(1.0f, 0.0f, 1.0f, 0.0f); + self.textContainerInset = UIEdgeInsetsMake(7.0f, 12.0f, 7.0f, 12.0f); [self ensurePlaceholderConstraints]; [self updatePlaceholderVisibility]; @@ -74,6 +70,15 @@ NS_ASSUME_NONNULL_BEGIN self.placeholderView.font = font; } +- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)isAnimated +{ + // When creating new lines, contentOffset is animated, but because because + // we are simultaneously resizing the text view, this can cause the + // text in the textview to be "too high" in the text view. + // Solution is to disable animation for setting content offset. + [super setContentOffset:contentOffset animated:NO]; +} + - (void)setContentInset:(UIEdgeInsets)contentInset { [super setContentInset:contentInset]; @@ -104,10 +109,12 @@ NS_ASSUME_NONNULL_BEGIN CGRect beginningTextRect = [self firstRectForRange:beginningTextRange]; CGFloat topInset = beginningTextRect.origin.y; + CGFloat leftInset = beginningTextRect.origin.x; + // we use Left instead of Leading, since it's based on the prior CGRect offset self.placeholderConstraints = @[ - [self.placeholderView autoPinLeadingToSuperviewMargin], - [self.placeholderView autoPinTrailingToSuperviewMargin], + [self.placeholderView autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:leftInset], + [self.placeholderView autoPinEdgeToSuperviewEdge:ALEdgeRight], [self.placeholderView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:topInset], ]; } @@ -178,7 +185,7 @@ NS_ASSUME_NONNULL_BEGIN [self updatePlaceholderVisibility]; - [self.textViewToolbarDelegate textViewDidChange]; + [self.textViewToolbarDelegate textViewDidChange:self]; } - (void)textViewDidEndEditing:(UITextView *)textView diff --git a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.h b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.h index 085e25d30..b1c5fb4e7 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.h @@ -47,8 +47,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)setInputTextViewDelegate:(id)value; - (NSString *)messageText; -- (void)setMessageText:(NSString *_Nullable)value; -- (void)clearTextMessage; +- (void)setMessageText:(NSString *_Nullable)value animated:(BOOL)isAnimated; +- (void)clearTextMessageAnimated:(BOOL)isAnimated; - (void)toggleDefaultKeyboard; - (void)updateFontSizes; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m index 9c6ece9cf..fbead8242 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m @@ -19,31 +19,25 @@ NS_ASSUME_NONNULL_BEGIN static void *kConversationInputTextViewObservingContext = &kConversationInputTextViewObservingContext; -static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5; + +const CGFloat kMinTextViewHeight = 36; +const CGFloat kMaxTextViewHeight = 98; #pragma mark - -@interface ConversationInputToolbar () +@interface ConversationInputToolbar () @property (nonatomic, readonly) ConversationStyle *conversationStyle; -@property (nonatomic, readonly) UIView *composeContainer; @property (nonatomic, readonly) ConversationInputTextView *inputTextView; -@property (nonatomic, readonly) UIStackView *contentStackView; +@property (nonatomic, readonly) UIStackView *contentRows; +@property (nonatomic, readonly) UIStackView *composeRow; @property (nonatomic, readonly) UIButton *attachmentButton; @property (nonatomic, readonly) UIButton *sendButton; @property (nonatomic, readonly) UIButton *voiceMemoButton; -@property (nonatomic, readonly) UIView *leftButtonWrapper; -@property (nonatomic, readonly) UIView *rightButtonWrapper; - -@property (nonatomic) BOOL shouldShowVoiceMemoButton; -@property (nonatomic) NSArray *contentContraints; -@property (nonatomic) NSValue *lastTextContentSize; -@property (nonatomic) CGFloat toolbarHeight; @property (nonatomic) CGFloat textViewHeight; +@property (nonatomic, readonly) NSLayoutConstraint *textViewHeightConstraint; #pragma mark - @@ -81,23 +75,11 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5; return self; } -- (void)dealloc -{ - [self removeKVOObservers]; -} - - (CGSize)intrinsicContentSize { - // Since we have `self.autoresizingMask = UIViewAutoresizingFlexibleHeight`, the intrinsicContentSize is used - // to determine the height of the rendered inputAccessoryView. - CGFloat height = self.toolbarHeight + ConversationInputToolbarBorderViewHeight; - if (self.quotedMessagePreview) { - height += self.quotedMessageTopMargin; - height += self.quotedMessagePreview.intrinsicContentSize.height; - } - CGSize newSize = CGSizeMake(self.bounds.size.width, height); - - return newSize; + // Since we have `self.autoresizingMask = UIViewAutoresizingFlexibleHeight`, we must specify + // an intrinsicContentSize. Specifying CGSize.zero causes the height to be determined by autolayout. + return CGSizeZero; } - (void)createContents @@ -107,10 +89,13 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5; if (UIAccessibilityIsReduceTransparencyEnabled()) { self.backgroundColor = [UIColor ows_toolbarBackgroundColor]; } else { - // We can mute the blur by making our background color more opaque. - self.backgroundColor = [[UIColor ows_toolbarBackgroundColor] colorWithAlphaComponent:0.6]; + UIBlurEffect *blurEffect; + // More muted blur effects look gray when overlayed on white. + blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; + + // We can make our blur effect more muted by increasing this background alpha. + self.backgroundColor = [[UIColor ows_toolbarBackgroundColor] colorWithAlphaComponent:0.4f]; - UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleExtraLight]; UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; [self addSubview:blurEffectView]; [blurEffectView autoPinEdgesToSuperviewEdges]; @@ -118,38 +103,13 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5; self.autoresizingMask = UIViewAutoresizingFlexibleHeight; - UIView *borderView = [UIView new]; - borderView.backgroundColor = [UIColor colorWithWhite:238 / 255.f alpha:1.f]; - [self addSubview:borderView]; - [borderView autoPinWidthToSuperview]; - [borderView autoPinEdgeToSuperviewEdge:ALEdgeTop]; - [borderView autoSetDimension:ALDimensionHeight toSize:ConversationInputToolbarBorderViewHeight]; - - _composeContainer = [UIView containerView]; - _contentStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ _composeContainer ]]; - _contentStackView.axis = UILayoutConstraintAxisVertical; - - [self addSubview:_contentStackView]; - [_contentStackView autoPinEdgesToSuperviewEdges]; - _inputTextView = [ConversationInputTextView new]; + self.inputTextView.layer.cornerRadius = kMinTextViewHeight / 2.0f; self.inputTextView.textViewToolbarDelegate = self; self.inputTextView.font = [UIFont ows_dynamicTypeBodyFont]; - [self.composeContainer addSubview:self.inputTextView]; - - // We want to be permissive about taps on the send and attachment buttons, - // so we use wrapper views that capture nearby taps. This is a lot easier - // than trying to manipulate the size of the buttons themselves, as you - // can't coordinate the layout of the button content (e.g. image or text) - // using iOS auto layout. - _leftButtonWrapper = [UIView containerView]; - [self.leftButtonWrapper - addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(leftButtonTapped:)]]; - [self.composeContainer addSubview:self.leftButtonWrapper]; - _rightButtonWrapper = [UIView containerView]; - [self.rightButtonWrapper - addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(rightButtonTapped:)]]; - [self.composeContainer addSubview:self.rightButtonWrapper]; + [self.inputTextView setContentHuggingHorizontalLow]; + + _textViewHeightConstraint = [self.inputTextView autoSetDimension:ALDimensionHeight toSize:kMinTextViewHeight]; _attachmentButton = [[UIButton alloc] init]; self.attachmentButton.accessibilityLabel @@ -159,51 +119,60 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5; [self.attachmentButton addTarget:self action:@selector(attachmentButtonPressed) forControlEvents:UIControlEventTouchUpInside]; - [self.attachmentButton setImage:[UIImage imageNamed:@"btnAttachments--blue"] forState:UIControlStateNormal]; - self.attachmentButton.contentEdgeInsets = UIEdgeInsetsMake(0, 3, 0, 3); - [self.leftButtonWrapper addSubview:self.attachmentButton]; + UIImage *attachmentImage = [UIImage imageNamed:@"btnAttachments--blue"]; + [self.attachmentButton setImage:[attachmentImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] + forState:UIControlStateNormal]; + self.attachmentButton.tintColor = UIColor.ows_navbarIconColor; + [self.attachmentButton autoSetDimensionsToSize:CGSizeMake(40, kMinTextViewHeight)]; - // TODO: Fix layout in this class. _sendButton = [UIButton buttonWithType:UIButtonTypeCustom]; [self.sendButton setTitle:NSLocalizedString(@"SEND_BUTTON_TITLE", @"Label for the send button in the conversation view.") forState:UIControlStateNormal]; - [self.sendButton setTitleColor:[UIColor ows_materialBlueColor] forState:UIControlStateNormal]; + [self.sendButton setTitleColor:UIColor.ows_signalBlueColor forState:UIControlStateNormal]; self.sendButton.titleLabel.textAlignment = NSTextAlignmentCenter; - self.sendButton.titleLabel.font = [UIFont ows_mediumFontWithSize:16.f]; + self.sendButton.titleLabel.font = [UIFont ows_mediumFontWithSize:17.f]; + self.sendButton.contentEdgeInsets = UIEdgeInsetsMake(0, 4, 0, 4); + [self.sendButton autoSetDimension:ALDimensionHeight toSize:kMinTextViewHeight]; [self.sendButton addTarget:self action:@selector(sendButtonPressed) forControlEvents:UIControlEventTouchUpInside]; - [self.rightButtonWrapper addSubview:self.sendButton]; UIImage *voiceMemoIcon = [UIImage imageNamed:@"voice-memo-button"]; OWSAssert(voiceMemoIcon); _voiceMemoButton = [UIButton buttonWithType:UIButtonTypeCustom]; [self.voiceMemoButton setImage:[voiceMemoIcon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal]; - self.voiceMemoButton.imageView.tintColor = [UIColor ows_materialBlueColor]; - [self.rightButtonWrapper addSubview:self.voiceMemoButton]; + self.voiceMemoButton.imageView.tintColor = UIColor.ows_navbarIconColor; + [self.voiceMemoButton autoSetDimensionsToSize:CGSizeMake(40, kMinTextViewHeight)]; // We want to be permissive about the voice message gesture, so we hang // the long press GR on the button's wrapper, not the button itself. UILongPressGestureRecognizer *longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; longPressGestureRecognizer.minimumPressDuration = 0; - longPressGestureRecognizer.delegate = self; - [self.rightButtonWrapper addGestureRecognizer:longPressGestureRecognizer]; + [self.voiceMemoButton addGestureRecognizer:longPressGestureRecognizer]; self.userInteractionEnabled = YES; - [self addKVOObservers]; + _composeRow = [[UIStackView alloc] + initWithArrangedSubviews:@[ self.attachmentButton, self.inputTextView, self.voiceMemoButton, self.sendButton ]]; + self.composeRow.axis = UILayoutConstraintAxisHorizontal; + self.composeRow.layoutMarginsRelativeArrangement = YES; + self.composeRow.layoutMargins = UIEdgeInsetsMake(6, 6, 6, 6); + self.composeRow.alignment = UIStackViewAlignmentBottom; + self.composeRow.spacing = 8; + + _contentRows = [[UIStackView alloc] initWithArrangedSubviews:@[ self.composeRow ]]; + self.contentRows.axis = UILayoutConstraintAxisVertical; - [self ensureShouldShowVoiceMemoButton]; + [self addSubview:self.contentRows]; + [self.contentRows autoPinEdgesToSuperviewEdges]; - [self ensureContentConstraints]; + [self ensureShouldShowVoiceMemoButtonAnimated:NO]; } - (void)updateFontSizes { self.inputTextView.font = [UIFont ows_dynamicTypeBodyFont]; - - [self ensureContentConstraints]; } - (void)setInputTextViewDelegate:(id)value @@ -221,18 +190,19 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5; return self.inputTextView.trimmedText; } -- (void)setMessageText:(NSString *_Nullable)value +- (void)setMessageText:(NSString *_Nullable)value animated:(BOOL)isAnimated { OWSAssert(self.inputTextView); self.inputTextView.text = value; - [self ensureShouldShowVoiceMemoButton]; + [self ensureShouldShowVoiceMemoButtonAnimated:isAnimated]; + [self updateHeightWithTextView:self.inputTextView]; } -- (void)clearTextMessage +- (void)clearTextMessageAnimated:(BOOL)isAnimated { - [self setMessageText:nil]; + [self setMessageText:nil animated:isAnimated]; [self.inputTextView.undoManager removeAllActions]; } @@ -256,17 +226,6 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5; [self.inputTextView reloadInputViews]; } -- (void)setShouldShowVoiceMemoButton:(BOOL)shouldShowVoiceMemoButton -{ - if (_shouldShowVoiceMemoButton == shouldShowVoiceMemoButton) { - return; - } - - _shouldShowVoiceMemoButton = shouldShowVoiceMemoButton; - - [self ensureContentConstraints]; -} - - (void)setQuotedReply:(nullable OWSQuotedReplyModel *)quotedReply { if (quotedReply == _quotedReply) { @@ -294,8 +253,8 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5; [wrapper addSubview:quotedMessagePreview]; [quotedMessagePreview autoPinToSuperviewMargins]; - // TODO animate - [self.contentStackView insertArrangedSubview:wrapper atIndex:0]; + [self.contentRows insertArrangedSubview:wrapper atIndex:0]; + self.quotedMessagePreview = wrapper; } @@ -306,9 +265,8 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5; - (void)clearQuotedMessagePreview { - // TODO animate if (self.quotedMessagePreview) { - [self.contentStackView removeArrangedSubview:self.quotedMessagePreview]; + [self.contentRows removeArrangedSubview:self.quotedMessagePreview]; [self.quotedMessagePreview removeFromSuperview]; self.quotedMessagePreview = nil; } @@ -329,106 +287,38 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5; return self.inputTextView.isFirstResponder; } -- (void)ensureContentConstraints +- (void)ensureShouldShowVoiceMemoButtonAnimated:(BOOL)isAnimated { - [NSLayoutConstraint deactivateConstraints:self.contentContraints]; - - const int textViewVInset = 5; - const int contentHInset = 6; - const int contentHSpacing = 6; - - // We want to grow the text input area to fit its content within reason. - const CGFloat kMinTextViewHeight = ceil(self.inputTextView.font.lineHeight - + self.inputTextView.textContainerInset.top + self.inputTextView.textContainerInset.bottom - + self.inputTextView.contentInset.top + self.inputTextView.contentInset.bottom); - // Exactly 4 lines of text with default sizing. - const CGFloat kMaxTextViewHeight = 98.f; - const CGFloat textViewDesiredHeight = (self.inputTextView.contentSize.height + self.inputTextView.contentInset.top - + self.inputTextView.contentInset.bottom); - const CGFloat textViewHeight = ceil(CGFloatClamp(textViewDesiredHeight, kMinTextViewHeight, kMaxTextViewHeight)); - const CGFloat kMinContentHeight = kMinTextViewHeight + textViewVInset * 2; - - self.textViewHeight = textViewHeight; - self.toolbarHeight = textViewHeight + textViewVInset * 2; - - self.leftButtonWrapper.hidden = NO; - self.inputTextView.hidden = NO; - self.voiceMemoButton.hidden = NO; - - UIButton *leftButton = self.attachmentButton; - UIButton *rightButton = (self.shouldShowVoiceMemoButton ? self.voiceMemoButton : self.sendButton); - UIButton *inactiveRightButton = (self.shouldShowVoiceMemoButton ? self.sendButton : self.voiceMemoButton); - leftButton.enabled = YES; - rightButton.enabled = YES; - inactiveRightButton.enabled = NO; - leftButton.hidden = NO; - rightButton.hidden = NO; - inactiveRightButton.hidden = YES; - - [leftButton setContentHuggingHigh]; - [rightButton setContentHuggingHigh]; - [leftButton setCompressionResistanceHigh]; - [rightButton setCompressionResistanceHigh]; - [self.inputTextView setCompressionResistanceLow]; - [self.inputTextView setContentHuggingLow]; - - OWSAssert(leftButton.superview == self.leftButtonWrapper); - OWSAssert(rightButton.superview == self.rightButtonWrapper); - - // The leading and trailing buttons should be center-aligned with the - // inputTextView when the inputTextView is at its minimum size. - // - // We want the leading and trailing buttons to hug the bottom of the input - // toolbar as the inputTextView expands. - // - // Therefore we fix the button heights to the size of the toolbar when - // inputTextView is at its minimum size. - // - // Additionally, we use "wrapper" views around the leading and trailing - // buttons to expand their hot area. - self.contentContraints = @[ - [self.leftButtonWrapper autoPinEdgeToSuperviewEdge:ALEdgeLeft], - [self.leftButtonWrapper autoPinEdgeToSuperviewEdge:ALEdgeTop], - [self.leftButtonWrapper autoPinBottomToSuperviewMarginWithInset:0], - - [leftButton autoSetDimension:ALDimensionHeight toSize:kMinContentHeight], - [leftButton autoPinLeadingToSuperviewMarginWithInset:contentHInset], - [leftButton autoPinTrailingToSuperviewMarginWithInset:contentHSpacing], - [leftButton autoPinEdgeToSuperviewEdge:ALEdgeBottom], - - [self.inputTextView autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:self.leftButtonWrapper], - [self.inputTextView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:textViewVInset], - [self.inputTextView autoPinBottomToSuperviewMarginWithInset:textViewVInset], - [self.inputTextView autoSetDimension:ALDimensionHeight toSize:textViewHeight], - - [self.rightButtonWrapper autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:self.inputTextView], - [self.rightButtonWrapper autoPinEdgeToSuperviewEdge:ALEdgeRight], - [self.rightButtonWrapper autoPinEdgeToSuperviewEdge:ALEdgeTop], - [self.rightButtonWrapper autoPinBottomToSuperviewMarginWithInset:0], - - [rightButton autoSetDimension:ALDimensionHeight toSize:kMinContentHeight], - [rightButton autoPinLeadingToSuperviewMarginWithInset:contentHSpacing], - [rightButton autoPinTrailingToSuperviewMarginWithInset:contentHInset], - [rightButton autoPinEdgeToSuperviewEdge:ALEdgeBottom] - ]; - - // Layout immediately, unless the input toolbar hasn't even been laid out yet. - if (self.bounds.size.width > 0 && self.bounds.size.height > 0) { + void (^updateBlock)(void) = ^{ + if (self.inputTextView.trimmedText.length > 0) { + if (!self.voiceMemoButton.isHidden) { + self.voiceMemoButton.hidden = YES; + } + + if (self.sendButton.isHidden) { + self.sendButton.hidden = NO; + } + } else { + if (self.voiceMemoButton.isHidden) { + self.voiceMemoButton.hidden = NO; + } + + if (!self.sendButton.isHidden) { + self.sendButton.hidden = YES; + } + } [self layoutIfNeeded]; - } -} + }; -- (void)ensureShouldShowVoiceMemoButton -{ - self.shouldShowVoiceMemoButton = self.inputTextView.trimmedText.length < 1; + if (isAnimated) { + [UIView animateWithDuration:0.1 animations:updateBlock]; + } else { + updateBlock(); + } } - (void)handleLongPress:(UIGestureRecognizer *)sender { - if (!self.shouldShowVoiceMemoButton) { - return; - } - switch (sender.state) { case UIGestureRecognizerStatePossible: case UIGestureRecognizerStateCancelled: @@ -481,17 +371,6 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5; } } -#pragma mark - UIGestureRecognizerDelegate - -- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch -{ - if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) { - return self.shouldShowVoiceMemoButton; - } else { - return YES; - } -} - #pragma mark - Voice Memo - (void)showVoiceMemoUI @@ -698,22 +577,6 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5; #pragma mark - Event Handlers -- (void)leftButtonTapped:(UIGestureRecognizer *)sender -{ - if (sender.state == UIGestureRecognizerStateRecognized) { - [self attachmentButtonPressed]; - } -} - -- (void)rightButtonTapped:(UIGestureRecognizer *)sender -{ - if (sender.state == UIGestureRecognizerStateRecognized) { - if (!self.shouldShowVoiceMemoButton) { - [self sendButtonPressed]; - } - } -} - - (void)sendButtonPressed { OWSAssert(self.inputToolbarDelegate); @@ -730,58 +593,33 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5; #pragma mark - ConversationTextViewToolbarDelegate -- (void)textViewDidChange +- (void)textViewDidChange:(UITextView *)textView { OWSAssert(self.inputToolbarDelegate); - - [self ensureShouldShowVoiceMemoButton]; + [self ensureShouldShowVoiceMemoButtonAnimated:YES]; + [self updateHeightWithTextView:textView]; } -#pragma mark - Text Input Sizing - -- (void)addKVOObservers +- (void)updateHeightWithTextView:(UITextView *)textView { - [self.inputTextView addObserver:self - forKeyPath:NSStringFromSelector(@selector(contentSize)) - options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew - context:kConversationInputTextViewObservingContext]; -} + // compute new height assuming width is unchanged + CGSize currentSize = textView.frame.size; + CGFloat newHeight = [self clampedHeightWithTextView:textView fixedWidth:currentSize.width]; -- (void)removeKVOObservers -{ - @try { - [self.inputTextView removeObserver:self - forKeyPath:NSStringFromSelector(@selector(contentSize)) - context:kConversationInputTextViewObservingContext]; - } @catch (NSException *__unused exception) { - // TODO: This try/catch can probably be safely removed. - OWSFail(@"%@ removeKVOObservers failed.", self.logTag); + if (newHeight != self.textViewHeight) { + self.textViewHeight = newHeight; + OWSAssert(self.textViewHeightConstraint); + self.textViewHeightConstraint.constant = newHeight; + [self invalidateIntrinsicContentSize]; } } -- (void)observeValueForKeyPath:(nullable NSString *)keyPath - ofObject:(nullable id)object - change:(nullable NSDictionary *)change - context:(nullable void *)context +- (CGFloat)clampedHeightWithTextView:(UITextView *)textView fixedWidth:(CGFloat)fixedWidth { - if (context == kConversationInputTextViewObservingContext) { - - if (object == self.inputTextView && [keyPath isEqualToString:NSStringFromSelector(@selector(contentSize))]) { - CGSize textContentSize = self.inputTextView.contentSize; - NSValue *_Nullable lastTextContentSize = self.lastTextContentSize; - self.lastTextContentSize = [NSValue valueWithCGSize:textContentSize]; - - // Update view constraints, but only when text content size changes. - // - // NOTE: We use a "fuzzy equals" comparison to avoid infinite recursion, - // since ensureContentConstraints can affect the text content size. - if (!lastTextContentSize || fabs(lastTextContentSize.CGSizeValue.width - textContentSize.width) > 0.1f - || fabs(lastTextContentSize.CGSizeValue.height - textContentSize.height) > 0.1f) { - [self ensureContentConstraints]; - [self invalidateIntrinsicContentSize]; - } - } - } + CGSize fixedWidthSize = CGSizeMake(fixedWidth, CGFLOAT_MAX); + CGSize contentSize = [textView sizeThatFits:fixedWidthSize]; + + return CGFloatClamp(contentSize.height, kMinTextViewHeight, kMaxTextViewHeight); } #pragma mark QuotedReplyPreviewViewDelegate diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 1294604a9..98c3af041 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -3980,7 +3980,7 @@ typedef enum : NSUInteger { [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { draft = [_thread currentDraftWithTransaction:transaction]; }]; - [self.inputToolbar setMessageText:draft]; + [self.inputToolbar setMessageText:draft animated:NO]; } - (void)saveDraft @@ -4262,8 +4262,11 @@ typedef enum : NSUInteger { CGFloat contentHeight = self.safeContentHeight; - CGFloat dstY - = MAX(0, contentHeight + self.collectionView.contentInset.bottom - self.collectionView.bounds.size.height); + // bottomLayoutGuide accounts for extra offset needed on iPhoneX + + CGFloat dstY = MAX(0, + contentHeight + self.collectionView.contentInset.bottom + self.bottomLayoutGuide.length + - self.collectionView.bounds.size.height); [self.collectionView setContentOffset:CGPointMake(0, dstY) animated:NO]; [self didScrollToBottom]; @@ -4417,7 +4420,7 @@ typedef enum : NSUInteger { if (updateKeyboardState) { [self.inputToolbar toggleDefaultKeyboard]; } - [self.inputToolbar clearTextMessage]; + [self.inputToolbar clearTextMessageAnimated:YES]; [self clearDraft]; if (didAddToProfileWhitelist) { [self ensureDynamicInteractions]; diff --git a/SignalMessaging/attachments/AttachmentApprovalViewController.swift b/SignalMessaging/attachments/AttachmentApprovalViewController.swift index f9b87c7ed..d0a0d5752 100644 --- a/SignalMessaging/attachments/AttachmentApprovalViewController.swift +++ b/SignalMessaging/attachments/AttachmentApprovalViewController.swift @@ -532,10 +532,13 @@ class CaptioningToolbar: UIView, UITextViewDelegate { textView.delegate = self textView.backgroundColor = UIColor.white - textView.layer.cornerRadius = 4.0 + textView.layer.cornerRadius = kMinTextViewHeight / 2 + textView.addBorder(with: UIColor.lightGray) textView.font = UIFont.ows_dynamicTypeBody textView.returnKeyType = .done + textView.textContainerInset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7) + textView.scrollIndicatorInsets = UIEdgeInsets(top: 5, left: 0, bottom: 5, right: 3) let sendTitle = NSLocalizedString("ATTACHMENT_APPROVAL_SEND_BUTTON", comment: "Label for 'send' button in the 'attachment approval' dialog.") sendButton.setTitle(sendTitle, for: .normal)