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 | 375 +++++------------- 7 files changed, 111 insertions(+), 270 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 960c724fb..a3e3286e8 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 5433e093c..ed7a9cb2a 100644 --- a/Signal/src/ViewControllers/MessagesViewController.m +++ b/Signal/src/ViewControllers/MessagesViewController.m @@ -97,14 +97,6 @@ typedef enum : NSUInteger { - (void)didPasteAttachment:(SignalAttachment * _Nullable)attachment; -- (void)voiceMemoGestureDidStart; - -- (void)voiceMemoGestureDidEnd; - -- (void)voiceMemoGestureDidCancel; - -- (void)voiceMemoGestureDidChange:(CGFloat)cancelAlpha; - @end #pragma mark - @@ -113,182 +105,110 @@ typedef enum : NSUInteger { @property (weak, nonatomic) id textViewPasteDelegate; -@property (nonatomic) BOOL shouldShowVoiceMemoButton; - -@property (nonatomic) UIView *voiceMemoButton; - -// This view serves as its own delegate but also needs to forward delegate events -// to JSQ. -@property (weak, nonatomic) id jsqDelegate; - -@property (nonatomic) BOOL isRecordingVoiceMemo; - -@property (nonatomic) CGPoint voiceMemoGestureStart; - @end #pragma mark - @implementation OWSMessagesComposerTextView -- (instancetype)init +- (BOOL)canBecomeFirstResponder { - self = [super init]; - if (!self) { - return self; - } - - [self commonInit]; + return YES; +} - return self; +- (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]); } -- (instancetype)initWithCoder:(NSCoder *)aDecoder +- (BOOL)canPerformAction:(SEL)action withSender:(id)sender { - self = [super initWithCoder:aDecoder]; - if (!self) { - return self; + if (action == @selector(paste:)) { + if ([self pasteboardHasPossibleAttachment]) { + return YES; + } } - - [self commonInit]; - - return self; + return [super canPerformAction:action withSender:sender]; } -- (instancetype)initWithFrame:(CGRect)frame +- (void)paste:(id)sender { - self = [super initWithFrame:frame]; - if (!self) { - return self; + 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; } - [self commonInit]; - - return self; + [super paste:sender]; } -- (void)commonInit -{ - self.delegate = self; - [self ensureShouldShowVoiceMemoButton]; -} +@end -- (void)setDelegate:(id)delegate -{ - if (delegate == self) { - [super setDelegate:delegate]; - } else { - self.jsqDelegate = delegate; - } -} +#pragma mark - -#pragma mark - UITextViewDelegate +@protocol OWSMessagesToolbarContentDelegate -- (BOOL)textViewShouldBeginEditing:(UITextView *)textView -{ - if ([self.jsqDelegate respondsToSelector:@selector(textViewShouldBeginEditing:)]) { - return [self.jsqDelegate textViewShouldBeginEditing:textView]; - } - return YES; -} +- (void)voiceMemoGestureDidStart; -- (BOOL)textViewShouldEndEditing:(UITextView *)textView -{ - if ([self.jsqDelegate respondsToSelector:@selector(textViewShouldEndEditing:)]) { - return [self.jsqDelegate textViewShouldEndEditing:textView]; - } - return YES; -} +- (void)voiceMemoGestureDidEnd; -- (void)textViewDidBeginEditing:(UITextView *)textView -{ - if ([self.jsqDelegate respondsToSelector:@selector(textViewDidBeginEditing:)]) { - [self.jsqDelegate textViewDidBeginEditing:textView]; - } -} +- (void)voiceMemoGestureDidCancel; -- (void)textViewDidEndEditing:(UITextView *)textView -{ - if ([self.jsqDelegate respondsToSelector:@selector(textViewDidEndEditing:)]) { - [self.jsqDelegate textViewDidEndEditing:textView]; - } -} +- (void)voiceMemoGestureDidChange:(CGFloat)cancelAlpha; -- (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; -} +@end -- (void)textViewDidChange:(UITextView *)textView -{ - if ([self.jsqDelegate respondsToSelector:@selector(textViewDidChange:)]) { - [self.jsqDelegate textViewDidChange:textView]; - } +#pragma mark - - [self ensureShouldShowVoiceMemoButton]; -} +@interface OWSMessagesToolbarContentView () -- (void)textViewDidChangeSelection:(UITextView *)textView -{ - if ([self.jsqDelegate respondsToSelector:@selector(textViewDidChangeSelection:)]) { - [self.jsqDelegate textViewDidChangeSelection:textView]; - } -} +@property (nonatomic, weak) id delegate; -- (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; -} +@property (nonatomic) BOOL shouldShowVoiceMemoButton; + +@property (nonatomic) UIButton *voiceMemoButton; + +@property (nonatomic) UIButton *sendButton; + +@property (nonatomic) BOOL isRecordingVoiceMemo; + +@property (nonatomic) CGPoint voiceMemoGestureStartLocation; + +@end + +#pragma mark - + +@implementation OWSMessagesToolbarContentView + +#pragma mark - Class methods -- (BOOL)textView:(UITextView *)textView - shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment - inRange:(NSRange)characterRange - interaction:(UITextItemInteraction)interaction ++ (UINib *)nib { - if ([self.jsqDelegate - respondsToSelector:@selector(textView:shouldInteractWithTextAttachment:inRange:interaction:)]) { - return [self.jsqDelegate textView:textView - shouldInteractWithTextAttachment:textAttachment - inRange:characterRange - interaction:interaction]; - } - return YES; + return [UINib nibWithNibName:NSStringFromClass([OWSMessagesToolbarContentView class]) + bundle:[NSBundle bundleForClass:[OWSMessagesToolbarContentView class]]]; } -- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange +- (void)ensureSubviews { - if ([self.jsqDelegate respondsToSelector:@selector(textView:shouldInteractWithURL:inRange:)]) { - return [self.jsqDelegate textView:textView shouldInteractWithURL:URL inRange:characterRange]; - } - return YES; + [self ensureShouldShowVoiceMemoButton]; + + [self ensureVoiceMemoButton]; } -- (BOOL)textView:(UITextView *)textView - shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment - inRange:(NSRange)characterRange +- (void)ensureEnabling { - if ([self.jsqDelegate respondsToSelector:@selector(textView:shouldInteractWithTextAttachment:inRange:)]) { - return - [self.jsqDelegate textView:textView shouldInteractWithTextAttachment:textAttachment inRange:characterRange]; - } - return YES; + [self ensureShouldShowVoiceMemoButton]; + + OWSAssert(self.voiceMemoButton.isEnabled == YES); + OWSAssert(self.sendButton.isEnabled == YES); } - (void)ensureShouldShowVoiceMemoButton { - self.shouldShowVoiceMemoButton = self.text.length < 1; + self.shouldShowVoiceMemoButton = self.textView.text.length < 1; } - (void)setShouldShowVoiceMemoButton:(BOOL)shouldShowVoiceMemoButton @@ -302,80 +222,38 @@ typedef enum : NSUInteger { [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]; + if (!self.sendButton) { + OWSAssert(self.rightBarButtonItem); - self.voiceMemoButton = button; - [self addSubview:button]; - [self layoutVoiceMemoButton]; - } else { - [self.voiceMemoButton removeFromSuperview]; - self.voiceMemoButton = nil; + self.sendButton = self.rightBarButtonItem; } -} - -- (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]; -} + 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; + } -- (void)layoutVoiceMemoButton -{ - if (!self.voiceMemoButton) { - return; + self.rightBarButtonItem = self.voiceMemoButton; + self.rightBarButtonItemWidth = [self.voiceMemoButton sizeThatFits:CGSizeZero].width; + } else { + self.rightBarButtonItem = self.sendButton; + self.rightBarButtonItemWidth = [self.sendButton sizeThatFits:CGSizeZero].width; } - 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 @@ -386,38 +264,38 @@ typedef enum : NSUInteger { case UIGestureRecognizerStateFailed: if (self.isRecordingVoiceMemo) { self.isRecordingVoiceMemo = NO; - [self.textViewPasteDelegate voiceMemoGestureDidCancel]; + [self.delegate voiceMemoGestureDidCancel]; } break; case UIGestureRecognizerStateBegan: if (self.isRecordingVoiceMemo) { self.isRecordingVoiceMemo = NO; - [self.textViewPasteDelegate voiceMemoGestureDidCancel]; + [self.delegate voiceMemoGestureDidCancel]; } [self resignFirstResponder]; self.isRecordingVoiceMemo = YES; - self.voiceMemoGestureStart = [sender locationInView:self]; - [self.textViewPasteDelegate voiceMemoGestureDidStart]; + self.voiceMemoGestureStartLocation = [sender locationInView:self]; + [self.delegate 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 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.textViewPasteDelegate voiceMemoGestureDidCancel]; + [self.delegate voiceMemoGestureDidCancel]; } else { - [self.textViewPasteDelegate voiceMemoGestureDidChange:cancelAlpha]; + [self.delegate voiceMemoGestureDidChange:cancelAlpha]; } } break; case UIGestureRecognizerStateEnded: if (self.isRecordingVoiceMemo) { self.isRecordingVoiceMemo = NO; - [self.textViewPasteDelegate voiceMemoGestureDidEnd]; + [self.delegate voiceMemoGestureDidEnd]; } break; } @@ -430,51 +308,6 @@ typedef enum : NSUInteger { } } -- (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 - - -@implementation OWSMessagesToolbarContentView - -#pragma mark - Class methods - -+ (UINib *)nib -{ - return [UINib nibWithNibName:NSStringFromClass([OWSMessagesToolbarContentView class]) - bundle:[NSBundle bundleForClass:[OWSMessagesToolbarContentView class]]]; -} - @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