Merge branch 'mkirk/tweak-sender-bar'

pull/1/head
Michael Kirk 7 years ago
commit dc036496b4

@ -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 NS_ASSUME_NONNULL_BEGIN
@ -18,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN
@protocol ConversationTextViewToolbarDelegate <NSObject> @protocol ConversationTextViewToolbarDelegate <NSObject>
- (void)textViewDidChange; - (void)textViewDidChange:(UITextView *)textView;
@end @end

@ -27,20 +27,17 @@ NS_ASSUME_NONNULL_BEGIN
self.delegate = self; self.delegate = self;
CGFloat cornerRadius = 6.0f; self.backgroundColor = [UIColor ows_light02Color];
self.layer.borderColor = [UIColor.ows_blackColor colorWithAlphaComponent:0.12f].CGColor;
self.backgroundColor = [UIColor whiteColor];
self.layer.borderColor = [UIColor lightGrayColor].CGColor;
self.layer.borderWidth = 0.5f; 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.scrollEnabled = YES;
self.scrollsToTop = NO; self.scrollsToTop = NO;
self.userInteractionEnabled = YES; self.userInteractionEnabled = YES;
self.font = [UIFont systemFontOfSize:16.0f]; self.font = [UIFont ows_dynamicTypeBodyFont];
self.textColor = [UIColor blackColor]; self.textColor = [UIColor blackColor];
self.textAlignment = NSTextAlignmentNatural; self.textAlignment = NSTextAlignmentNatural;
@ -51,14 +48,13 @@ NS_ASSUME_NONNULL_BEGIN
self.placeholderView = [UILabel new]; self.placeholderView = [UILabel new];
self.placeholderView.text = NSLocalizedString(@"new_message", @""); self.placeholderView.text = NSLocalizedString(@"new_message", @"");
self.placeholderView.textColor = [UIColor lightGrayColor]; self.placeholderView.textColor = UIColor.ows_light35Color;
self.placeholderView.userInteractionEnabled = NO; self.placeholderView.userInteractionEnabled = NO;
[self addSubview:self.placeholderView]; [self addSubview:self.placeholderView];
// We need to do these steps _after_ placeholderView is configured. // We need to do these steps _after_ placeholderView is configured.
self.font = [UIFont ows_dynamicTypeBodyFont]; self.font = [UIFont ows_dynamicTypeBodyFont];
self.textContainerInset = UIEdgeInsetsMake(4.0f, 2.0f, 4.0f, 2.0f); self.textContainerInset = UIEdgeInsetsMake(7.0f, 12.0f, 7.0f, 12.0f);
self.contentInset = UIEdgeInsetsMake(1.0f, 0.0f, 1.0f, 0.0f);
[self ensurePlaceholderConstraints]; [self ensurePlaceholderConstraints];
[self updatePlaceholderVisibility]; [self updatePlaceholderVisibility];
@ -74,6 +70,15 @@ NS_ASSUME_NONNULL_BEGIN
self.placeholderView.font = font; 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 - (void)setContentInset:(UIEdgeInsets)contentInset
{ {
[super setContentInset:contentInset]; [super setContentInset:contentInset];
@ -104,10 +109,12 @@ NS_ASSUME_NONNULL_BEGIN
CGRect beginningTextRect = [self firstRectForRange:beginningTextRange]; CGRect beginningTextRect = [self firstRectForRange:beginningTextRange];
CGFloat topInset = beginningTextRect.origin.y; 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.placeholderConstraints = @[
[self.placeholderView autoPinLeadingToSuperviewMargin], [self.placeholderView autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:leftInset],
[self.placeholderView autoPinTrailingToSuperviewMargin], [self.placeholderView autoPinEdgeToSuperviewEdge:ALEdgeRight],
[self.placeholderView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:topInset], [self.placeholderView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:topInset],
]; ];
} }
@ -178,7 +185,7 @@ NS_ASSUME_NONNULL_BEGIN
[self updatePlaceholderVisibility]; [self updatePlaceholderVisibility];
[self.textViewToolbarDelegate textViewDidChange]; [self.textViewToolbarDelegate textViewDidChange:self];
} }
- (void)textViewDidEndEditing:(UITextView *)textView - (void)textViewDidEndEditing:(UITextView *)textView

@ -47,8 +47,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)setInputTextViewDelegate:(id<ConversationInputTextViewDelegate>)value; - (void)setInputTextViewDelegate:(id<ConversationInputTextViewDelegate>)value;
- (NSString *)messageText; - (NSString *)messageText;
- (void)setMessageText:(NSString *_Nullable)value; - (void)setMessageText:(NSString *_Nullable)value animated:(BOOL)isAnimated;
- (void)clearTextMessage; - (void)clearTextMessageAnimated:(BOOL)isAnimated;
- (void)toggleDefaultKeyboard; - (void)toggleDefaultKeyboard;
- (void)updateFontSizes; - (void)updateFontSizes;

@ -19,31 +19,25 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
static void *kConversationInputTextViewObservingContext = &kConversationInputTextViewObservingContext; static void *kConversationInputTextViewObservingContext = &kConversationInputTextViewObservingContext;
static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
const CGFloat kMinTextViewHeight = 36;
const CGFloat kMaxTextViewHeight = 98;
#pragma mark - #pragma mark -
@interface ConversationInputToolbar () <UIGestureRecognizerDelegate, @interface ConversationInputToolbar () <ConversationTextViewToolbarDelegate, QuotedReplyPreviewDelegate>
ConversationTextViewToolbarDelegate,
QuotedReplyPreviewDelegate>
@property (nonatomic, readonly) ConversationStyle *conversationStyle; @property (nonatomic, readonly) ConversationStyle *conversationStyle;
@property (nonatomic, readonly) UIView *composeContainer;
@property (nonatomic, readonly) ConversationInputTextView *inputTextView; @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 *attachmentButton;
@property (nonatomic, readonly) UIButton *sendButton; @property (nonatomic, readonly) UIButton *sendButton;
@property (nonatomic, readonly) UIButton *voiceMemoButton; @property (nonatomic, readonly) UIButton *voiceMemoButton;
@property (nonatomic, readonly) UIView *leftButtonWrapper;
@property (nonatomic, readonly) UIView *rightButtonWrapper;
@property (nonatomic) BOOL shouldShowVoiceMemoButton;
@property (nonatomic) NSArray<NSLayoutConstraint *> *contentContraints;
@property (nonatomic) NSValue *lastTextContentSize;
@property (nonatomic) CGFloat toolbarHeight;
@property (nonatomic) CGFloat textViewHeight; @property (nonatomic) CGFloat textViewHeight;
@property (nonatomic, readonly) NSLayoutConstraint *textViewHeightConstraint;
#pragma mark - #pragma mark -
@ -81,23 +75,11 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
return self; return self;
} }
- (void)dealloc
{
[self removeKVOObservers];
}
- (CGSize)intrinsicContentSize - (CGSize)intrinsicContentSize
{ {
// Since we have `self.autoresizingMask = UIViewAutoresizingFlexibleHeight`, the intrinsicContentSize is used // Since we have `self.autoresizingMask = UIViewAutoresizingFlexibleHeight`, we must specify
// to determine the height of the rendered inputAccessoryView. // an intrinsicContentSize. Specifying CGSize.zero causes the height to be determined by autolayout.
CGFloat height = self.toolbarHeight + ConversationInputToolbarBorderViewHeight; return CGSizeZero;
if (self.quotedMessagePreview) {
height += self.quotedMessageTopMargin;
height += self.quotedMessagePreview.intrinsicContentSize.height;
}
CGSize newSize = CGSizeMake(self.bounds.size.width, height);
return newSize;
} }
- (void)createContents - (void)createContents
@ -107,10 +89,13 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
if (UIAccessibilityIsReduceTransparencyEnabled()) { if (UIAccessibilityIsReduceTransparencyEnabled()) {
self.backgroundColor = [UIColor ows_toolbarBackgroundColor]; self.backgroundColor = [UIColor ows_toolbarBackgroundColor];
} else { } else {
// We can mute the blur by making our background color more opaque. UIBlurEffect *blurEffect;
self.backgroundColor = [[UIColor ows_toolbarBackgroundColor] colorWithAlphaComponent:0.6]; // 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]; UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
[self addSubview:blurEffectView]; [self addSubview:blurEffectView];
[blurEffectView autoPinEdgesToSuperviewEdges]; [blurEffectView autoPinEdgesToSuperviewEdges];
@ -118,38 +103,13 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
self.autoresizingMask = UIViewAutoresizingFlexibleHeight; 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]; _inputTextView = [ConversationInputTextView new];
self.inputTextView.layer.cornerRadius = kMinTextViewHeight / 2.0f;
self.inputTextView.textViewToolbarDelegate = self; self.inputTextView.textViewToolbarDelegate = self;
self.inputTextView.font = [UIFont ows_dynamicTypeBodyFont]; self.inputTextView.font = [UIFont ows_dynamicTypeBodyFont];
[self.composeContainer addSubview:self.inputTextView]; [self.inputTextView setContentHuggingHorizontalLow];
// We want to be permissive about taps on the send and attachment buttons, _textViewHeightConstraint = [self.inputTextView autoSetDimension:ALDimensionHeight toSize:kMinTextViewHeight];
// 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];
_attachmentButton = [[UIButton alloc] init]; _attachmentButton = [[UIButton alloc] init];
self.attachmentButton.accessibilityLabel self.attachmentButton.accessibilityLabel
@ -159,51 +119,60 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
[self.attachmentButton addTarget:self [self.attachmentButton addTarget:self
action:@selector(attachmentButtonPressed) action:@selector(attachmentButtonPressed)
forControlEvents:UIControlEventTouchUpInside]; forControlEvents:UIControlEventTouchUpInside];
[self.attachmentButton setImage:[UIImage imageNamed:@"btnAttachments--blue"] forState:UIControlStateNormal]; UIImage *attachmentImage = [UIImage imageNamed:@"btnAttachments--blue"];
self.attachmentButton.contentEdgeInsets = UIEdgeInsetsMake(0, 3, 0, 3); [self.attachmentButton setImage:[attachmentImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]
[self.leftButtonWrapper addSubview:self.attachmentButton]; forState:UIControlStateNormal];
self.attachmentButton.tintColor = UIColor.ows_navbarIconColor;
[self.attachmentButton autoSetDimensionsToSize:CGSizeMake(40, kMinTextViewHeight)];
// TODO: Fix layout in this class.
_sendButton = [UIButton buttonWithType:UIButtonTypeCustom]; _sendButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.sendButton [self.sendButton
setTitle:NSLocalizedString(@"SEND_BUTTON_TITLE", @"Label for the send button in the conversation view.") setTitle:NSLocalizedString(@"SEND_BUTTON_TITLE", @"Label for the send button in the conversation view.")
forState:UIControlStateNormal]; 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.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.sendButton addTarget:self action:@selector(sendButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[self.rightButtonWrapper addSubview:self.sendButton];
UIImage *voiceMemoIcon = [UIImage imageNamed:@"voice-memo-button"]; UIImage *voiceMemoIcon = [UIImage imageNamed:@"voice-memo-button"];
OWSAssert(voiceMemoIcon); OWSAssert(voiceMemoIcon);
_voiceMemoButton = [UIButton buttonWithType:UIButtonTypeCustom]; _voiceMemoButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.voiceMemoButton setImage:[voiceMemoIcon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] [self.voiceMemoButton setImage:[voiceMemoIcon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]
forState:UIControlStateNormal]; forState:UIControlStateNormal];
self.voiceMemoButton.imageView.tintColor = [UIColor ows_materialBlueColor]; self.voiceMemoButton.imageView.tintColor = UIColor.ows_navbarIconColor;
[self.rightButtonWrapper addSubview:self.voiceMemoButton]; [self.voiceMemoButton autoSetDimensionsToSize:CGSizeMake(40, kMinTextViewHeight)];
// We want to be permissive about the voice message gesture, so we hang // 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. // the long press GR on the button's wrapper, not the button itself.
UILongPressGestureRecognizer *longPressGestureRecognizer = UILongPressGestureRecognizer *longPressGestureRecognizer =
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
longPressGestureRecognizer.minimumPressDuration = 0; longPressGestureRecognizer.minimumPressDuration = 0;
longPressGestureRecognizer.delegate = self; [self.voiceMemoButton addGestureRecognizer:longPressGestureRecognizer];
[self.rightButtonWrapper addGestureRecognizer:longPressGestureRecognizer];
self.userInteractionEnabled = YES; 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 - (void)updateFontSizes
{ {
self.inputTextView.font = [UIFont ows_dynamicTypeBodyFont]; self.inputTextView.font = [UIFont ows_dynamicTypeBodyFont];
[self ensureContentConstraints];
} }
- (void)setInputTextViewDelegate:(id<ConversationInputTextViewDelegate>)value - (void)setInputTextViewDelegate:(id<ConversationInputTextViewDelegate>)value
@ -221,18 +190,19 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
return self.inputTextView.trimmedText; return self.inputTextView.trimmedText;
} }
- (void)setMessageText:(NSString *_Nullable)value - (void)setMessageText:(NSString *_Nullable)value animated:(BOOL)isAnimated
{ {
OWSAssert(self.inputTextView); OWSAssert(self.inputTextView);
self.inputTextView.text = value; 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]; [self.inputTextView.undoManager removeAllActions];
} }
@ -256,17 +226,6 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
[self.inputTextView reloadInputViews]; [self.inputTextView reloadInputViews];
} }
- (void)setShouldShowVoiceMemoButton:(BOOL)shouldShowVoiceMemoButton
{
if (_shouldShowVoiceMemoButton == shouldShowVoiceMemoButton) {
return;
}
_shouldShowVoiceMemoButton = shouldShowVoiceMemoButton;
[self ensureContentConstraints];
}
- (void)setQuotedReply:(nullable OWSQuotedReplyModel *)quotedReply - (void)setQuotedReply:(nullable OWSQuotedReplyModel *)quotedReply
{ {
if (quotedReply == _quotedReply) { if (quotedReply == _quotedReply) {
@ -294,8 +253,8 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
[wrapper addSubview:quotedMessagePreview]; [wrapper addSubview:quotedMessagePreview];
[quotedMessagePreview autoPinToSuperviewMargins]; [quotedMessagePreview autoPinToSuperviewMargins];
// TODO animate [self.contentRows insertArrangedSubview:wrapper atIndex:0];
[self.contentStackView insertArrangedSubview:wrapper atIndex:0];
self.quotedMessagePreview = wrapper; self.quotedMessagePreview = wrapper;
} }
@ -306,9 +265,8 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
- (void)clearQuotedMessagePreview - (void)clearQuotedMessagePreview
{ {
// TODO animate
if (self.quotedMessagePreview) { if (self.quotedMessagePreview) {
[self.contentStackView removeArrangedSubview:self.quotedMessagePreview]; [self.contentRows removeArrangedSubview:self.quotedMessagePreview];
[self.quotedMessagePreview removeFromSuperview]; [self.quotedMessagePreview removeFromSuperview];
self.quotedMessagePreview = nil; self.quotedMessagePreview = nil;
} }
@ -329,106 +287,38 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
return self.inputTextView.isFirstResponder; return self.inputTextView.isFirstResponder;
} }
- (void)ensureContentConstraints - (void)ensureShouldShowVoiceMemoButtonAnimated:(BOOL)isAnimated
{ {
[NSLayoutConstraint deactivateConstraints:self.contentContraints]; void (^updateBlock)(void) = ^{
if (self.inputTextView.trimmedText.length > 0) {
const int textViewVInset = 5; if (!self.voiceMemoButton.isHidden) {
const int contentHInset = 6; self.voiceMemoButton.hidden = YES;
const int contentHSpacing = 6; }
// We want to grow the text input area to fit its content within reason. if (self.sendButton.isHidden) {
const CGFloat kMinTextViewHeight = ceil(self.inputTextView.font.lineHeight self.sendButton.hidden = NO;
+ self.inputTextView.textContainerInset.top + self.inputTextView.textContainerInset.bottom }
+ self.inputTextView.contentInset.top + self.inputTextView.contentInset.bottom); } else {
// Exactly 4 lines of text with default sizing. if (self.voiceMemoButton.isHidden) {
const CGFloat kMaxTextViewHeight = 98.f; self.voiceMemoButton.hidden = NO;
const CGFloat textViewDesiredHeight = (self.inputTextView.contentSize.height + self.inputTextView.contentInset.top }
+ self.inputTextView.contentInset.bottom);
const CGFloat textViewHeight = ceil(CGFloatClamp(textViewDesiredHeight, kMinTextViewHeight, kMaxTextViewHeight)); if (!self.sendButton.isHidden) {
const CGFloat kMinContentHeight = kMinTextViewHeight + textViewVInset * 2; self.sendButton.hidden = YES;
}
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) {
[self layoutIfNeeded]; [self layoutIfNeeded];
} };
}
- (void)ensureShouldShowVoiceMemoButton if (isAnimated) {
{ [UIView animateWithDuration:0.1 animations:updateBlock];
self.shouldShowVoiceMemoButton = self.inputTextView.trimmedText.length < 1; } else {
updateBlock();
}
} }
- (void)handleLongPress:(UIGestureRecognizer *)sender - (void)handleLongPress:(UIGestureRecognizer *)sender
{ {
if (!self.shouldShowVoiceMemoButton) {
return;
}
switch (sender.state) { switch (sender.state) {
case UIGestureRecognizerStatePossible: case UIGestureRecognizerStatePossible:
case UIGestureRecognizerStateCancelled: 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 #pragma mark - Voice Memo
- (void)showVoiceMemoUI - (void)showVoiceMemoUI
@ -698,22 +577,6 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
#pragma mark - Event Handlers #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 - (void)sendButtonPressed
{ {
OWSAssert(self.inputToolbarDelegate); OWSAssert(self.inputToolbarDelegate);
@ -730,58 +593,33 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
#pragma mark - ConversationTextViewToolbarDelegate #pragma mark - ConversationTextViewToolbarDelegate
- (void)textViewDidChange - (void)textViewDidChange:(UITextView *)textView
{ {
OWSAssert(self.inputToolbarDelegate); OWSAssert(self.inputToolbarDelegate);
[self ensureShouldShowVoiceMemoButtonAnimated:YES];
[self ensureShouldShowVoiceMemoButton]; [self updateHeightWithTextView:textView];
} }
#pragma mark - Text Input Sizing - (void)updateHeightWithTextView:(UITextView *)textView
- (void)addKVOObservers
{ {
[self.inputTextView addObserver:self // compute new height assuming width is unchanged
forKeyPath:NSStringFromSelector(@selector(contentSize)) CGSize currentSize = textView.frame.size;
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew CGFloat newHeight = [self clampedHeightWithTextView:textView fixedWidth:currentSize.width];
context:kConversationInputTextViewObservingContext];
}
- (void)removeKVOObservers if (newHeight != self.textViewHeight) {
{ self.textViewHeight = newHeight;
@try { OWSAssert(self.textViewHeightConstraint);
[self.inputTextView removeObserver:self self.textViewHeightConstraint.constant = newHeight;
forKeyPath:NSStringFromSelector(@selector(contentSize)) [self invalidateIntrinsicContentSize];
context:kConversationInputTextViewObservingContext];
} @catch (NSException *__unused exception) {
// TODO: This try/catch can probably be safely removed.
OWSFail(@"%@ removeKVOObservers failed.", self.logTag);
} }
} }
- (void)observeValueForKeyPath:(nullable NSString *)keyPath - (CGFloat)clampedHeightWithTextView:(UITextView *)textView fixedWidth:(CGFloat)fixedWidth
ofObject:(nullable id)object
change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
context:(nullable void *)context
{ {
if (context == kConversationInputTextViewObservingContext) { CGSize fixedWidthSize = CGSizeMake(fixedWidth, CGFLOAT_MAX);
CGSize contentSize = [textView sizeThatFits:fixedWidthSize];
if (object == self.inputTextView && [keyPath isEqualToString:NSStringFromSelector(@selector(contentSize))]) {
CGSize textContentSize = self.inputTextView.contentSize; return CGFloatClamp(contentSize.height, kMinTextViewHeight, kMaxTextViewHeight);
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];
}
}
}
} }
#pragma mark QuotedReplyPreviewViewDelegate #pragma mark QuotedReplyPreviewViewDelegate

@ -3980,7 +3980,7 @@ typedef enum : NSUInteger {
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
draft = [_thread currentDraftWithTransaction:transaction]; draft = [_thread currentDraftWithTransaction:transaction];
}]; }];
[self.inputToolbar setMessageText:draft]; [self.inputToolbar setMessageText:draft animated:NO];
} }
- (void)saveDraft - (void)saveDraft
@ -4262,8 +4262,11 @@ typedef enum : NSUInteger {
CGFloat contentHeight = self.safeContentHeight; CGFloat contentHeight = self.safeContentHeight;
CGFloat dstY // bottomLayoutGuide accounts for extra offset needed on iPhoneX
= MAX(0, contentHeight + self.collectionView.contentInset.bottom - self.collectionView.bounds.size.height);
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.collectionView setContentOffset:CGPointMake(0, dstY) animated:NO];
[self didScrollToBottom]; [self didScrollToBottom];
@ -4417,7 +4420,7 @@ typedef enum : NSUInteger {
if (updateKeyboardState) { if (updateKeyboardState) {
[self.inputToolbar toggleDefaultKeyboard]; [self.inputToolbar toggleDefaultKeyboard];
} }
[self.inputToolbar clearTextMessage]; [self.inputToolbar clearTextMessageAnimated:YES];
[self clearDraft]; [self clearDraft];
if (didAddToProfileWhitelist) { if (didAddToProfileWhitelist) {
[self ensureDynamicInteractions]; [self ensureDynamicInteractions];

@ -532,10 +532,13 @@ class CaptioningToolbar: UIView, UITextViewDelegate {
textView.delegate = self textView.delegate = self
textView.backgroundColor = UIColor.white textView.backgroundColor = UIColor.white
textView.layer.cornerRadius = 4.0 textView.layer.cornerRadius = kMinTextViewHeight / 2
textView.addBorder(with: UIColor.lightGray) textView.addBorder(with: UIColor.lightGray)
textView.font = UIFont.ows_dynamicTypeBody textView.font = UIFont.ows_dynamicTypeBody
textView.returnKeyType = .done 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.") let sendTitle = NSLocalizedString("ATTACHMENT_APPROVAL_SEND_BUTTON", comment: "Label for 'send' button in the 'attachment approval' dialog.")
sendButton.setTitle(sendTitle, for: .normal) sendButton.setTitle(sendTitle, for: .normal)

Loading…
Cancel
Save