From 8ecdc8a2eb53d526cf27070b4eabf4b7e1a9f7c0 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 5 May 2017 10:10:28 -0400 Subject: [PATCH] Move voice memo button to send button. // FREEBIE --- .../voice-memo-button.imageset/Contents.json | 6 +- ...button-25.png => voice-memo-button-32.png} | Bin 1496 -> 1588 bytes .../voice-memo-button-50.png | Bin 1766 -> 0 bytes .../voice-memo-button-64.png | Bin 0 -> 1959 bytes .../voice-memo-button-75.png | Bin 2124 -> 0 bytes .../voice-memo-button-96.png | Bin 0 -> 2467 bytes .../ViewControllers/MessagesViewController.m | 543 +++++++----------- 7 files changed, 195 insertions(+), 354 deletions(-) rename Signal/Images.xcassets/voice-memo-button.imageset/{voice-memo-button-25.png => voice-memo-button-32.png} (56%) delete mode 100644 Signal/Images.xcassets/voice-memo-button.imageset/voice-memo-button-50.png create mode 100644 Signal/Images.xcassets/voice-memo-button.imageset/voice-memo-button-64.png delete mode 100644 Signal/Images.xcassets/voice-memo-button.imageset/voice-memo-button-75.png create mode 100644 Signal/Images.xcassets/voice-memo-button.imageset/voice-memo-button-96.png diff --git a/Signal/Images.xcassets/voice-memo-button.imageset/Contents.json b/Signal/Images.xcassets/voice-memo-button.imageset/Contents.json index 960c724fb6..a3e3286e8f 100644 --- a/Signal/Images.xcassets/voice-memo-button.imageset/Contents.json +++ b/Signal/Images.xcassets/voice-memo-button.imageset/Contents.json @@ -2,17 +2,17 @@ "images" : [ { "idiom" : "universal", - "filename" : "voice-memo-button-25.png", + "filename" : "voice-memo-button-32.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "voice-memo-button-50.png", + "filename" : "voice-memo-button-64.png", "scale" : "2x" }, { "idiom" : "universal", - "filename" : "voice-memo-button-75.png", + "filename" : "voice-memo-button-96.png", "scale" : "3x" } ], diff --git a/Signal/Images.xcassets/voice-memo-button.imageset/voice-memo-button-25.png b/Signal/Images.xcassets/voice-memo-button.imageset/voice-memo-button-32.png similarity index 56% rename from Signal/Images.xcassets/voice-memo-button.imageset/voice-memo-button-25.png rename to Signal/Images.xcassets/voice-memo-button.imageset/voice-memo-button-32.png index 5b7221dc46a1d2359309dbed2eb287c137656841..7c8fe54aa5dd772d6e88daed4d465be0a616de65 100644 GIT binary patch delta 524 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 E0Qc|XKmY&$ literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/voice-memo-button.imageset/voice-memo-button-75.png b/Signal/Images.xcassets/voice-memo-button.imageset/voice-memo-button-75.png deleted file mode 100644 index 775e470fdbfb39559fd531e8ded4cf624a2b234e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2124 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 Z%C$y~UAwBCSF8Uv`TGRWD!n5P{15dF%~AjW literal 0 HcmV?d00001 diff --git a/Signal/src/ViewControllers/MessagesViewController.m b/Signal/src/ViewControllers/MessagesViewController.m index 5433e093cd..ed7a9cb2ae 100644 --- a/Signal/src/ViewControllers/MessagesViewController.m +++ b/Signal/src/ViewControllers/MessagesViewController.m @@ -97,6 +97,60 @@ typedef enum : NSUInteger { - (void)didPasteAttachment:(SignalAttachment * _Nullable)attachment; +@end + +#pragma mark - + +@interface OWSMessagesComposerTextView () + +@property (weak, nonatomic) id textViewPasteDelegate; + +@end + +#pragma mark - + +@implementation OWSMessagesComposerTextView + +- (BOOL)canBecomeFirstResponder +{ + return YES; +} + +- (BOOL)pasteboardHasPossibleAttachment +{ + // We don't want to load/convert images more than once so we + // only do a cursory validation pass at this time. + return ([SignalAttachment pasteboardHasPossibleAttachment] && ![SignalAttachment pasteboardHasText]); +} + +- (BOOL)canPerformAction:(SEL)action withSender:(id)sender +{ + if (action == @selector(paste:)) { + if ([self pasteboardHasPossibleAttachment]) { + return YES; + } + } + return [super canPerformAction:action withSender:sender]; +} + +- (void)paste:(id)sender +{ + if ([self pasteboardHasPossibleAttachment]) { + SignalAttachment *attachment = [SignalAttachment attachmentFromPasteboard]; + // Note: attachment might be nil or have an error at this point; that's fine. + [self.textViewPasteDelegate didPasteAttachment:attachment]; + return; + } + + [super paste:sender]; +} + +@end + +#pragma mark - + +@protocol OWSMessagesToolbarContentDelegate + - (void)voiceMemoGestureDidStart; - (void)voiceMemoGestureDidEnd; @@ -109,357 +163,19 @@ typedef enum : NSUInteger { #pragma mark - -@interface OWSMessagesComposerTextView () +@interface OWSMessagesToolbarContentView () -@property (weak, nonatomic) id textViewPasteDelegate; +@property (nonatomic, weak) id delegate; @property (nonatomic) BOOL shouldShowVoiceMemoButton; -@property (nonatomic) UIView *voiceMemoButton; +@property (nonatomic) UIButton *voiceMemoButton; -// This view serves as its own delegate but also needs to forward delegate events -// to JSQ. -@property (weak, nonatomic) id jsqDelegate; +@property (nonatomic) UIButton *sendButton; @property (nonatomic) BOOL isRecordingVoiceMemo; -@property (nonatomic) CGPoint voiceMemoGestureStart; - -@end - -#pragma mark - - -@implementation OWSMessagesComposerTextView - -- (instancetype)init -{ - self = [super init]; - if (!self) { - return self; - } - - [self commonInit]; - - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)aDecoder -{ - self = [super initWithCoder:aDecoder]; - if (!self) { - return self; - } - - [self commonInit]; - - return self; -} - -- (instancetype)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (!self) { - return self; - } - - [self commonInit]; - - return self; -} - -- (void)commonInit -{ - self.delegate = self; - [self ensureShouldShowVoiceMemoButton]; -} - -- (void)setDelegate:(id)delegate -{ - if (delegate == self) { - [super setDelegate:delegate]; - } else { - self.jsqDelegate = delegate; - } -} - -#pragma mark - UITextViewDelegate - -- (BOOL)textViewShouldBeginEditing:(UITextView *)textView -{ - if ([self.jsqDelegate respondsToSelector:@selector(textViewShouldBeginEditing:)]) { - return [self.jsqDelegate textViewShouldBeginEditing:textView]; - } - return YES; -} - -- (BOOL)textViewShouldEndEditing:(UITextView *)textView -{ - if ([self.jsqDelegate respondsToSelector:@selector(textViewShouldEndEditing:)]) { - return [self.jsqDelegate textViewShouldEndEditing:textView]; - } - return YES; -} - -- (void)textViewDidBeginEditing:(UITextView *)textView -{ - if ([self.jsqDelegate respondsToSelector:@selector(textViewDidBeginEditing:)]) { - [self.jsqDelegate textViewDidBeginEditing:textView]; - } -} - -- (void)textViewDidEndEditing:(UITextView *)textView -{ - if ([self.jsqDelegate respondsToSelector:@selector(textViewDidEndEditing:)]) { - [self.jsqDelegate textViewDidEndEditing:textView]; - } -} - -- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text -{ - if ([self.jsqDelegate respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementText:)]) { - return [self.jsqDelegate textView:textView shouldChangeTextInRange:range replacementText:text]; - } - return YES; -} - -- (void)textViewDidChange:(UITextView *)textView -{ - if ([self.jsqDelegate respondsToSelector:@selector(textViewDidChange:)]) { - [self.jsqDelegate textViewDidChange:textView]; - } - - [self ensureShouldShowVoiceMemoButton]; -} - -- (void)textViewDidChangeSelection:(UITextView *)textView -{ - if ([self.jsqDelegate respondsToSelector:@selector(textViewDidChangeSelection:)]) { - [self.jsqDelegate textViewDidChangeSelection:textView]; - } -} - -- (BOOL)textView:(UITextView *)textView - shouldInteractWithURL:(NSURL *)URL - inRange:(NSRange)characterRange - interaction:(UITextItemInteraction)interaction -{ - if ([self.jsqDelegate respondsToSelector:@selector(textView:shouldInteractWithURL:inRange:interaction:)]) { - return [self.jsqDelegate textView:textView - shouldInteractWithURL:URL - inRange:characterRange - interaction:interaction]; - } - return YES; -} - -- (BOOL)textView:(UITextView *)textView - shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment - inRange:(NSRange)characterRange - interaction:(UITextItemInteraction)interaction -{ - if ([self.jsqDelegate - respondsToSelector:@selector(textView:shouldInteractWithTextAttachment:inRange:interaction:)]) { - return [self.jsqDelegate textView:textView - shouldInteractWithTextAttachment:textAttachment - inRange:characterRange - interaction:interaction]; - } - return YES; -} - -- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange -{ - if ([self.jsqDelegate respondsToSelector:@selector(textView:shouldInteractWithURL:inRange:)]) { - return [self.jsqDelegate textView:textView shouldInteractWithURL:URL inRange:characterRange]; - } - return YES; -} - -- (BOOL)textView:(UITextView *)textView - shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment - inRange:(NSRange)characterRange -{ - if ([self.jsqDelegate respondsToSelector:@selector(textView:shouldInteractWithTextAttachment:inRange:)]) { - return - [self.jsqDelegate textView:textView shouldInteractWithTextAttachment:textAttachment inRange:characterRange]; - } - return YES; -} - -- (void)ensureShouldShowVoiceMemoButton -{ - self.shouldShowVoiceMemoButton = self.text.length < 1; -} - -- (void)setShouldShowVoiceMemoButton:(BOOL)shouldShowVoiceMemoButton -{ - if (_shouldShowVoiceMemoButton == shouldShowVoiceMemoButton) { - return; - } - - _shouldShowVoiceMemoButton = shouldShowVoiceMemoButton; - - [self ensureVoiceMemoButton]; -} - -- (CGFloat)voiceMemoButtonSize -{ - return 25; -} - -- (void)ensureVoiceMemoButton -{ - if (!self.superview) { - return; - } - - if (self.shouldShowVoiceMemoButton) { - [self.voiceMemoButton removeFromSuperview]; - self.voiceMemoButton = nil; - - UIView *button = [UIView new]; - button.frame = CGRectMake(0, 0, self.voiceMemoButtonSize, self.voiceMemoButtonSize); - [button addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self - action:@selector(handleLongPress:)]]; - button.userInteractionEnabled = YES; - - UIImage *icon = [UIImage imageNamed:@"voice-memo-button"]; - OWSAssert(icon); - UIImageView *imageView = [[UIImageView alloc] initWithImage:icon]; - imageView.layer.opacity = 0.8f; - [button addSubview:imageView]; - - self.voiceMemoButton = button; - [self addSubview:button]; - [self layoutVoiceMemoButton]; - } else { - [self.voiceMemoButton removeFromSuperview]; - self.voiceMemoButton = nil; - } -} - -- (void)ensureSubviews -{ - [self ensureVoiceMemoButton]; -} - -- (void)setFrame:(CGRect)frame -{ - [super setFrame:frame]; - - [self layoutVoiceMemoButton]; -} - -- (void)setBounds:(CGRect)bounds -{ - [super setBounds:bounds]; - - [self layoutVoiceMemoButton]; -} - -- (void)setCenter:(CGPoint)center -{ - [super setCenter:center]; - - [self layoutVoiceMemoButton]; -} - -- (void)layoutVoiceMemoButton -{ - if (!self.voiceMemoButton) { - return; - } - CGRect buttonFrame = CGRectMake(floor(self.frame.size.width - (self.voiceMemoButtonSize + 5)), - floor((self.frame.size.height - self.voiceMemoButtonSize) * 0.5f), - self.voiceMemoButtonSize, - self.voiceMemoButtonSize); - buttonFrame = [self.voiceMemoButton.superview convertRect:buttonFrame fromView:self]; - self.voiceMemoButton.frame = buttonFrame; - [self.voiceMemoButton.superview bringSubviewToFront:self.voiceMemoButton]; -} - -- (void)handleLongPress:(UIGestureRecognizer *)sender -{ - switch (sender.state) { - case UIGestureRecognizerStatePossible: - case UIGestureRecognizerStateCancelled: - case UIGestureRecognizerStateFailed: - if (self.isRecordingVoiceMemo) { - self.isRecordingVoiceMemo = NO; - [self.textViewPasteDelegate voiceMemoGestureDidCancel]; - } - break; - case UIGestureRecognizerStateBegan: - if (self.isRecordingVoiceMemo) { - self.isRecordingVoiceMemo = NO; - [self.textViewPasteDelegate voiceMemoGestureDidCancel]; - } - [self resignFirstResponder]; - self.isRecordingVoiceMemo = YES; - self.voiceMemoGestureStart = [sender locationInView:self]; - [self.textViewPasteDelegate voiceMemoGestureDidStart]; - break; - case UIGestureRecognizerStateChanged: - if (self.isRecordingVoiceMemo) { - CGPoint location = [sender locationInView:self]; - CGFloat offset = MAX(0, self.voiceMemoGestureStart.x - location.x); - const CGFloat kCancelOffsetPoints = 50.f; - CGFloat cancelAlpha = offset / kCancelOffsetPoints; - BOOL isCancelled = cancelAlpha >= 1.f; - if (isCancelled) { - self.isRecordingVoiceMemo = NO; - [self.textViewPasteDelegate voiceMemoGestureDidCancel]; - } else { - [self.textViewPasteDelegate voiceMemoGestureDidChange:cancelAlpha]; - } - } - break; - case UIGestureRecognizerStateEnded: - if (self.isRecordingVoiceMemo) { - self.isRecordingVoiceMemo = NO; - [self.textViewPasteDelegate voiceMemoGestureDidEnd]; - } - break; - } -} - -- (void)cancelVoiceMemoIfNecessary -{ - if (self.isRecordingVoiceMemo) { - self.isRecordingVoiceMemo = NO; - } -} - -- (BOOL)canBecomeFirstResponder { - return YES; -} - -- (BOOL)pasteboardHasPossibleAttachment -{ - // We don't want to load/convert images more than once so we - // only do a cursory validation pass at this time. - return ([SignalAttachment pasteboardHasPossibleAttachment] && ![SignalAttachment pasteboardHasText]); -} - -- (BOOL)canPerformAction:(SEL)action withSender:(id)sender { - if (action == @selector(paste:)) { - if ([self pasteboardHasPossibleAttachment]) { - return YES; - } - } - return [super canPerformAction:action withSender:sender]; -} - -- (void)paste:(id)sender { - if ([self pasteboardHasPossibleAttachment]) { - SignalAttachment *attachment = [SignalAttachment attachmentFromPasteboard]; - // Note: attachment might be nil or have an error at this point; that's fine. - [self.textViewPasteDelegate didPasteAttachment:attachment]; - return; - } - - [super paste:sender]; -} +@property (nonatomic) CGPoint voiceMemoGestureStartLocation; @end @@ -475,6 +191,123 @@ typedef enum : NSUInteger { bundle:[NSBundle bundleForClass:[OWSMessagesToolbarContentView class]]]; } +- (void)ensureSubviews +{ + [self ensureShouldShowVoiceMemoButton]; + + [self ensureVoiceMemoButton]; +} + +- (void)ensureEnabling +{ + [self ensureShouldShowVoiceMemoButton]; + + OWSAssert(self.voiceMemoButton.isEnabled == YES); + OWSAssert(self.sendButton.isEnabled == YES); +} + +- (void)ensureShouldShowVoiceMemoButton +{ + self.shouldShowVoiceMemoButton = self.textView.text.length < 1; +} + +- (void)setShouldShowVoiceMemoButton:(BOOL)shouldShowVoiceMemoButton +{ + if (_shouldShowVoiceMemoButton == shouldShowVoiceMemoButton) { + return; + } + + _shouldShowVoiceMemoButton = shouldShowVoiceMemoButton; + + [self ensureVoiceMemoButton]; +} + +- (void)ensureVoiceMemoButton +{ + if (!self.superview) { + return; + } + + if (!self.sendButton) { + OWSAssert(self.rightBarButtonItem); + + self.sendButton = self.rightBarButtonItem; + } + + if (self.shouldShowVoiceMemoButton) { + if (!self.voiceMemoButton) { + UIImage *icon = [UIImage imageNamed:@"voice-memo-button"]; + OWSAssert(icon); + UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; + [button setImage:[icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] + forState:UIControlStateNormal]; + button.imageView.tintColor = [UIColor ows_materialBlueColor]; + [button + addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self + action:@selector(handleLongPress:)]]; + self.voiceMemoButton = button; + } + + self.rightBarButtonItem = self.voiceMemoButton; + self.rightBarButtonItemWidth = [self.voiceMemoButton sizeThatFits:CGSizeZero].width; + } else { + self.rightBarButtonItem = self.sendButton; + self.rightBarButtonItemWidth = [self.sendButton sizeThatFits:CGSizeZero].width; + } +} + +- (void)handleLongPress:(UIGestureRecognizer *)sender +{ + switch (sender.state) { + case UIGestureRecognizerStatePossible: + case UIGestureRecognizerStateCancelled: + case UIGestureRecognizerStateFailed: + if (self.isRecordingVoiceMemo) { + self.isRecordingVoiceMemo = NO; + [self.delegate voiceMemoGestureDidCancel]; + } + break; + case UIGestureRecognizerStateBegan: + if (self.isRecordingVoiceMemo) { + self.isRecordingVoiceMemo = NO; + [self.delegate voiceMemoGestureDidCancel]; + } + [self resignFirstResponder]; + self.isRecordingVoiceMemo = YES; + self.voiceMemoGestureStartLocation = [sender locationInView:self]; + [self.delegate voiceMemoGestureDidStart]; + break; + case UIGestureRecognizerStateChanged: + if (self.isRecordingVoiceMemo) { + CGPoint location = [sender locationInView:self]; + CGFloat offset = MAX(0, self.voiceMemoGestureStartLocation.x - location.x); + const CGFloat kCancelOffsetPoints = 60.f; + CGFloat cancelAlpha = offset / kCancelOffsetPoints; + BOOL isCancelled = cancelAlpha >= 1.f; + if (isCancelled) { + self.isRecordingVoiceMemo = NO; + [self.delegate voiceMemoGestureDidCancel]; + } else { + [self.delegate voiceMemoGestureDidChange:cancelAlpha]; + } + } + break; + case UIGestureRecognizerStateEnded: + if (self.isRecordingVoiceMemo) { + self.isRecordingVoiceMemo = NO; + [self.delegate voiceMemoGestureDidEnd]; + } + break; + } +} + +- (void)cancelVoiceMemoIfNecessary +{ + if (self.isRecordingVoiceMemo) { + self.isRecordingVoiceMemo = NO; + } +} + @end #pragma mark - @@ -495,6 +328,11 @@ typedef enum : NSUInteger { @implementation OWSMessagesInputToolbar +- (void)toggleSendButtonEnabled +{ + // Do nothing; disables JSQ's control over send button enabling. +} + - (JSQMessagesToolbarContentView *)loadToolbarContentView { NSArray *views = [[OWSMessagesToolbarContentView nib] instantiateWithOwner:nil options:nil]; @@ -504,11 +342,6 @@ typedef enum : NSUInteger { return view; } -- (CGFloat)voiceMemoButtonSize -{ - return 25; -} - - (void)showVoiceMemoUI { OWSAssert([NSThread isMainThread]); @@ -534,7 +367,6 @@ typedef enum : NSUInteger { UIImageView *imageView = [[UIImageView alloc] initWithImage:[icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]]; imageView.tintColor = [UIColor ows_materialBlueColor]; - // imageView.layer.opacity = 0.8f; [self.voiceMemoUI addSubview:imageView]; UILabel *cancelLabel = [UILabel new]; @@ -647,6 +479,7 @@ typedef enum : NSUInteger { @interface MessagesViewController () { @@ -1045,7 +878,7 @@ typedef enum : NSUInteger { [self resetContentAndLayout]; - [((OWSMessagesComposerTextView *)self.inputToolbar.contentView.textView)ensureSubviews]; + [((OWSMessagesToolbarContentView *)self.inputToolbar.contentView)ensureSubviews]; } - (void)resetContentAndLayout @@ -1494,6 +1327,7 @@ typedef enum : NSUInteger { OWSAssert(self.inputToolbar.contentView.textView); self.inputToolbar.contentView.textView.pasteDelegate = self; ((OWSMessagesComposerTextView *) self.inputToolbar.contentView.textView).textViewPasteDelegate = self; + ((OWSMessagesToolbarContentView *)self.inputToolbar.contentView).delegate = self; } // Overiding JSQMVC layout defaults @@ -3600,6 +3434,8 @@ typedef enum : NSUInteger { completion:nil]; } +#pragma mark - OWSMessagesToolbarContentDelegate + - (void)voiceMemoGestureDidStart { OWSAssert([NSThread isMainThread]); @@ -3641,11 +3477,16 @@ typedef enum : NSUInteger { { OWSAssert([NSThread isMainThread]); - [((OWSMessagesComposerTextView *)self.inputToolbar.contentView.textView)cancelVoiceMemoIfNecessary]; + [((OWSMessagesToolbarContentView *)self.inputToolbar.contentView)cancelVoiceMemoIfNecessary]; [((OWSMessagesInputToolbar *)self.inputToolbar) hideVoiceMemoUI:NO]; [self cancelRecordingVoiceMemo]; } +- (void)textViewDidChange:(UITextView *)textView +{ + [((OWSMessagesToolbarContentView *)self.inputToolbar.contentView)ensureEnabling]; +} + #pragma mark - UIScrollViewDelegate - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView