Move voice memo button to send button.

// FREEBIE
pull/1/head
Matthew Chen 7 years ago
parent c34d61b93f
commit 8ecdc8a2eb

@ -2,17 +2,17 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"filename" : "voice-memo-button-25.png", "filename" : "voice-memo-button-32.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"filename" : "voice-memo-button-50.png", "filename" : "voice-memo-button-64.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"filename" : "voice-memo-button-75.png", "filename" : "voice-memo-button-96.png",
"scale" : "3x" "scale" : "3x"
} }
], ],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

@ -97,14 +97,6 @@ typedef enum : NSUInteger {
- (void)didPasteAttachment:(SignalAttachment * _Nullable)attachment; - (void)didPasteAttachment:(SignalAttachment * _Nullable)attachment;
- (void)voiceMemoGestureDidStart;
- (void)voiceMemoGestureDidEnd;
- (void)voiceMemoGestureDidCancel;
- (void)voiceMemoGestureDidChange:(CGFloat)cancelAlpha;
@end @end
#pragma mark - #pragma mark -
@ -113,182 +105,110 @@ typedef enum : NSUInteger {
@property (weak, nonatomic) id<OWSTextViewPasteDelegate> textViewPasteDelegate; @property (weak, nonatomic) id<OWSTextViewPasteDelegate> 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<UITextViewDelegate> jsqDelegate;
@property (nonatomic) BOOL isRecordingVoiceMemo;
@property (nonatomic) CGPoint voiceMemoGestureStart;
@end @end
#pragma mark - #pragma mark -
@implementation OWSMessagesComposerTextView @implementation OWSMessagesComposerTextView
- (instancetype)init - (BOOL)canBecomeFirstResponder
{ {
self = [super init]; return YES;
if (!self) { }
return self;
}
[self commonInit];
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 (action == @selector(paste:)) {
if (!self) { if ([self pasteboardHasPossibleAttachment]) {
return self; return YES;
}
} }
return [super canPerformAction:action withSender:sender];
[self commonInit];
return self;
} }
- (instancetype)initWithFrame:(CGRect)frame - (void)paste:(id)sender
{ {
self = [super initWithFrame:frame]; if ([self pasteboardHasPossibleAttachment]) {
if (!self) { SignalAttachment *attachment = [SignalAttachment attachmentFromPasteboard];
return self; // Note: attachment might be nil or have an error at this point; that's fine.
[self.textViewPasteDelegate didPasteAttachment:attachment];
return;
} }
[self commonInit]; [super paste:sender];
return self;
} }
- (void)commonInit @end
{
self.delegate = self;
[self ensureShouldShowVoiceMemoButton];
}
- (void)setDelegate:(id<UITextViewDelegate>)delegate #pragma mark -
{
if (delegate == self) {
[super setDelegate:delegate];
} else {
self.jsqDelegate = delegate;
}
}
#pragma mark - UITextViewDelegate @protocol OWSMessagesToolbarContentDelegate <NSObject>
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView - (void)voiceMemoGestureDidStart;
{
if ([self.jsqDelegate respondsToSelector:@selector(textViewShouldBeginEditing:)]) {
return [self.jsqDelegate textViewShouldBeginEditing:textView];
}
return YES;
}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView - (void)voiceMemoGestureDidEnd;
{
if ([self.jsqDelegate respondsToSelector:@selector(textViewShouldEndEditing:)]) {
return [self.jsqDelegate textViewShouldEndEditing:textView];
}
return YES;
}
- (void)textViewDidBeginEditing:(UITextView *)textView - (void)voiceMemoGestureDidCancel;
{
if ([self.jsqDelegate respondsToSelector:@selector(textViewDidBeginEditing:)]) {
[self.jsqDelegate textViewDidBeginEditing:textView];
}
}
- (void)textViewDidEndEditing:(UITextView *)textView - (void)voiceMemoGestureDidChange:(CGFloat)cancelAlpha;
{
if ([self.jsqDelegate respondsToSelector:@selector(textViewDidEndEditing:)]) {
[self.jsqDelegate textViewDidEndEditing:textView];
}
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text @end
{
if ([self.jsqDelegate respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementText:)]) {
return [self.jsqDelegate textView:textView shouldChangeTextInRange:range replacementText:text];
}
return YES;
}
- (void)textViewDidChange:(UITextView *)textView #pragma mark -
{
if ([self.jsqDelegate respondsToSelector:@selector(textViewDidChange:)]) {
[self.jsqDelegate textViewDidChange:textView];
}
[self ensureShouldShowVoiceMemoButton]; @interface OWSMessagesToolbarContentView ()
}
- (void)textViewDidChangeSelection:(UITextView *)textView @property (nonatomic, weak) id<OWSMessagesToolbarContentDelegate> delegate;
{
if ([self.jsqDelegate respondsToSelector:@selector(textViewDidChangeSelection:)]) {
[self.jsqDelegate textViewDidChangeSelection:textView];
}
}
- (BOOL)textView:(UITextView *)textView @property (nonatomic) BOOL shouldShowVoiceMemoButton;
shouldInteractWithURL:(NSURL *)URL
inRange:(NSRange)characterRange @property (nonatomic) UIButton *voiceMemoButton;
interaction:(UITextItemInteraction)interaction
{ @property (nonatomic) UIButton *sendButton;
if ([self.jsqDelegate respondsToSelector:@selector(textView:shouldInteractWithURL:inRange:interaction:)]) {
return [self.jsqDelegate textView:textView @property (nonatomic) BOOL isRecordingVoiceMemo;
shouldInteractWithURL:URL
inRange:characterRange @property (nonatomic) CGPoint voiceMemoGestureStartLocation;
interaction:interaction];
} @end
return YES;
} #pragma mark -
@implementation OWSMessagesToolbarContentView
#pragma mark - Class methods
- (BOOL)textView:(UITextView *)textView + (UINib *)nib
shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment
inRange:(NSRange)characterRange
interaction:(UITextItemInteraction)interaction
{ {
if ([self.jsqDelegate return [UINib nibWithNibName:NSStringFromClass([OWSMessagesToolbarContentView class])
respondsToSelector:@selector(textView:shouldInteractWithTextAttachment:inRange:interaction:)]) { bundle:[NSBundle bundleForClass:[OWSMessagesToolbarContentView class]]];
return [self.jsqDelegate textView:textView
shouldInteractWithTextAttachment:textAttachment
inRange:characterRange
interaction:interaction];
}
return YES;
} }
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange - (void)ensureSubviews
{ {
if ([self.jsqDelegate respondsToSelector:@selector(textView:shouldInteractWithURL:inRange:)]) { [self ensureShouldShowVoiceMemoButton];
return [self.jsqDelegate textView:textView shouldInteractWithURL:URL inRange:characterRange];
} [self ensureVoiceMemoButton];
return YES;
} }
- (BOOL)textView:(UITextView *)textView - (void)ensureEnabling
shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment
inRange:(NSRange)characterRange
{ {
if ([self.jsqDelegate respondsToSelector:@selector(textView:shouldInteractWithTextAttachment:inRange:)]) { [self ensureShouldShowVoiceMemoButton];
return
[self.jsqDelegate textView:textView shouldInteractWithTextAttachment:textAttachment inRange:characterRange]; OWSAssert(self.voiceMemoButton.isEnabled == YES);
} OWSAssert(self.sendButton.isEnabled == YES);
return YES;
} }
- (void)ensureShouldShowVoiceMemoButton - (void)ensureShouldShowVoiceMemoButton
{ {
self.shouldShowVoiceMemoButton = self.text.length < 1; self.shouldShowVoiceMemoButton = self.textView.text.length < 1;
} }
- (void)setShouldShowVoiceMemoButton:(BOOL)shouldShowVoiceMemoButton - (void)setShouldShowVoiceMemoButton:(BOOL)shouldShowVoiceMemoButton
@ -302,80 +222,38 @@ typedef enum : NSUInteger {
[self ensureVoiceMemoButton]; [self ensureVoiceMemoButton];
} }
- (CGFloat)voiceMemoButtonSize
{
return 25;
}
- (void)ensureVoiceMemoButton - (void)ensureVoiceMemoButton
{ {
if (!self.superview) { if (!self.superview) {
return; return;
} }
if (self.shouldShowVoiceMemoButton) { if (!self.sendButton) {
[self.voiceMemoButton removeFromSuperview]; OWSAssert(self.rightBarButtonItem);
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.sendButton = self.rightBarButtonItem;
[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]; if (self.shouldShowVoiceMemoButton) {
} if (!self.voiceMemoButton) {
UIImage *icon = [UIImage imageNamed:@"voice-memo-button"];
- (void)setBounds:(CGRect)bounds OWSAssert(icon);
{ UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[super setBounds:bounds]; [button setImage:[icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]
forState:UIControlStateNormal];
[self layoutVoiceMemoButton]; button.imageView.tintColor = [UIColor ows_materialBlueColor];
} [button
addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self
- (void)setCenter:(CGPoint)center action:@selector(handleLongPress:)]];
{ self.voiceMemoButton = button;
[super setCenter:center]; }
[self layoutVoiceMemoButton];
}
- (void)layoutVoiceMemoButton self.rightBarButtonItem = self.voiceMemoButton;
{ self.rightBarButtonItemWidth = [self.voiceMemoButton sizeThatFits:CGSizeZero].width;
if (!self.voiceMemoButton) { } else {
return; 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 - (void)handleLongPress:(UIGestureRecognizer *)sender
@ -386,38 +264,38 @@ typedef enum : NSUInteger {
case UIGestureRecognizerStateFailed: case UIGestureRecognizerStateFailed:
if (self.isRecordingVoiceMemo) { if (self.isRecordingVoiceMemo) {
self.isRecordingVoiceMemo = NO; self.isRecordingVoiceMemo = NO;
[self.textViewPasteDelegate voiceMemoGestureDidCancel]; [self.delegate voiceMemoGestureDidCancel];
} }
break; break;
case UIGestureRecognizerStateBegan: case UIGestureRecognizerStateBegan:
if (self.isRecordingVoiceMemo) { if (self.isRecordingVoiceMemo) {
self.isRecordingVoiceMemo = NO; self.isRecordingVoiceMemo = NO;
[self.textViewPasteDelegate voiceMemoGestureDidCancel]; [self.delegate voiceMemoGestureDidCancel];
} }
[self resignFirstResponder]; [self resignFirstResponder];
self.isRecordingVoiceMemo = YES; self.isRecordingVoiceMemo = YES;
self.voiceMemoGestureStart = [sender locationInView:self]; self.voiceMemoGestureStartLocation = [sender locationInView:self];
[self.textViewPasteDelegate voiceMemoGestureDidStart]; [self.delegate voiceMemoGestureDidStart];
break; break;
case UIGestureRecognizerStateChanged: case UIGestureRecognizerStateChanged:
if (self.isRecordingVoiceMemo) { if (self.isRecordingVoiceMemo) {
CGPoint location = [sender locationInView:self]; CGPoint location = [sender locationInView:self];
CGFloat offset = MAX(0, self.voiceMemoGestureStart.x - location.x); CGFloat offset = MAX(0, self.voiceMemoGestureStartLocation.x - location.x);
const CGFloat kCancelOffsetPoints = 50.f; const CGFloat kCancelOffsetPoints = 60.f;
CGFloat cancelAlpha = offset / kCancelOffsetPoints; CGFloat cancelAlpha = offset / kCancelOffsetPoints;
BOOL isCancelled = cancelAlpha >= 1.f; BOOL isCancelled = cancelAlpha >= 1.f;
if (isCancelled) { if (isCancelled) {
self.isRecordingVoiceMemo = NO; self.isRecordingVoiceMemo = NO;
[self.textViewPasteDelegate voiceMemoGestureDidCancel]; [self.delegate voiceMemoGestureDidCancel];
} else { } else {
[self.textViewPasteDelegate voiceMemoGestureDidChange:cancelAlpha]; [self.delegate voiceMemoGestureDidChange:cancelAlpha];
} }
} }
break; break;
case UIGestureRecognizerStateEnded: case UIGestureRecognizerStateEnded:
if (self.isRecordingVoiceMemo) { if (self.isRecordingVoiceMemo) {
self.isRecordingVoiceMemo = NO; self.isRecordingVoiceMemo = NO;
[self.textViewPasteDelegate voiceMemoGestureDidEnd]; [self.delegate voiceMemoGestureDidEnd];
} }
break; 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 @end
#pragma mark - #pragma mark -
@ -495,6 +328,11 @@ typedef enum : NSUInteger {
@implementation OWSMessagesInputToolbar @implementation OWSMessagesInputToolbar
- (void)toggleSendButtonEnabled
{
// Do nothing; disables JSQ's control over send button enabling.
}
- (JSQMessagesToolbarContentView *)loadToolbarContentView { - (JSQMessagesToolbarContentView *)loadToolbarContentView {
NSArray *views = [[OWSMessagesToolbarContentView nib] instantiateWithOwner:nil NSArray *views = [[OWSMessagesToolbarContentView nib] instantiateWithOwner:nil
options:nil]; options:nil];
@ -504,11 +342,6 @@ typedef enum : NSUInteger {
return view; return view;
} }
- (CGFloat)voiceMemoButtonSize
{
return 25;
}
- (void)showVoiceMemoUI - (void)showVoiceMemoUI
{ {
OWSAssert([NSThread isMainThread]); OWSAssert([NSThread isMainThread]);
@ -534,7 +367,6 @@ typedef enum : NSUInteger {
UIImageView *imageView = UIImageView *imageView =
[[UIImageView alloc] initWithImage:[icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]]; [[UIImageView alloc] initWithImage:[icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]];
imageView.tintColor = [UIColor ows_materialBlueColor]; imageView.tintColor = [UIColor ows_materialBlueColor];
// imageView.layer.opacity = 0.8f;
[self.voiceMemoUI addSubview:imageView]; [self.voiceMemoUI addSubview:imageView];
UILabel *cancelLabel = [UILabel new]; UILabel *cancelLabel = [UILabel new];
@ -647,6 +479,7 @@ typedef enum : NSUInteger {
@interface MessagesViewController () <JSQMessagesComposerTextViewPasteDelegate, @interface MessagesViewController () <JSQMessagesComposerTextViewPasteDelegate,
OWSTextViewPasteDelegate, OWSTextViewPasteDelegate,
OWSMessagesToolbarContentDelegate,
OWSConversationSettingsViewDelegate, OWSConversationSettingsViewDelegate,
UIDocumentMenuDelegate, UIDocumentMenuDelegate,
UIDocumentPickerDelegate> { UIDocumentPickerDelegate> {
@ -1045,7 +878,7 @@ typedef enum : NSUInteger {
[self resetContentAndLayout]; [self resetContentAndLayout];
[((OWSMessagesComposerTextView *)self.inputToolbar.contentView.textView)ensureSubviews]; [((OWSMessagesToolbarContentView *)self.inputToolbar.contentView)ensureSubviews];
} }
- (void)resetContentAndLayout - (void)resetContentAndLayout
@ -1494,6 +1327,7 @@ typedef enum : NSUInteger {
OWSAssert(self.inputToolbar.contentView.textView); OWSAssert(self.inputToolbar.contentView.textView);
self.inputToolbar.contentView.textView.pasteDelegate = self; self.inputToolbar.contentView.textView.pasteDelegate = self;
((OWSMessagesComposerTextView *) self.inputToolbar.contentView.textView).textViewPasteDelegate = self; ((OWSMessagesComposerTextView *) self.inputToolbar.contentView.textView).textViewPasteDelegate = self;
((OWSMessagesToolbarContentView *)self.inputToolbar.contentView).delegate = self;
} }
// Overiding JSQMVC layout defaults // Overiding JSQMVC layout defaults
@ -3600,6 +3434,8 @@ typedef enum : NSUInteger {
completion:nil]; completion:nil];
} }
#pragma mark - OWSMessagesToolbarContentDelegate
- (void)voiceMemoGestureDidStart - (void)voiceMemoGestureDidStart
{ {
OWSAssert([NSThread isMainThread]); OWSAssert([NSThread isMainThread]);
@ -3641,11 +3477,16 @@ typedef enum : NSUInteger {
{ {
OWSAssert([NSThread isMainThread]); OWSAssert([NSThread isMainThread]);
[((OWSMessagesComposerTextView *)self.inputToolbar.contentView.textView)cancelVoiceMemoIfNecessary]; [((OWSMessagesToolbarContentView *)self.inputToolbar.contentView)cancelVoiceMemoIfNecessary];
[((OWSMessagesInputToolbar *)self.inputToolbar) hideVoiceMemoUI:NO]; [((OWSMessagesInputToolbar *)self.inputToolbar) hideVoiceMemoUI:NO];
[self cancelRecordingVoiceMemo]; [self cancelRecordingVoiceMemo];
} }
- (void)textViewDidChange:(UITextView *)textView
{
[((OWSMessagesToolbarContentView *)self.inputToolbar.contentView)ensureEnabling];
}
#pragma mark - UIScrollViewDelegate #pragma mark - UIScrollViewDelegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView

Loading…
Cancel
Save