session-ios/Signal/src/ViewControllers/ConversationView/OWSMessagesToolbarContentVi...

219 lines
7.6 KiB
Objective-C

//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSMessagesToolbarContentView.h"
#import "UIColor+OWS.h"
@interface OWSMessagesToolbarContentView () <UIGestureRecognizerDelegate>
@property (nonatomic) BOOL shouldShowVoiceMemoButton;
@property (nonatomic, nullable) UIButton *voiceMemoButton;
@property (nonatomic, nullable) UIButton *sendButton;
@property (nonatomic) BOOL isRecordingVoiceMemo;
@property (nonatomic) CGPoint voiceMemoGestureStartLocation;
@end
#pragma mark -
@implementation OWSMessagesToolbarContentView
#pragma mark - Class methods
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([OWSMessagesToolbarContentView class])
bundle:[NSBundle bundleForClass:[OWSMessagesToolbarContentView class]]];
}
- (void)ensureSubviews
{
if (!self.sendButton) {
OWSAssert(self.rightBarButtonItem);
self.sendButton = self.rightBarButtonItem;
}
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];
// We want to be permissive about the voice message gesture, so we:
//
// * Add the gesture recognizer to the button's superview instead of the button.
// * Filter the touches that the gesture recognizer receives by serving as its
// delegate.
UILongPressGestureRecognizer *longPressGestureRecognizer =
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
longPressGestureRecognizer.minimumPressDuration = 0;
longPressGestureRecognizer.delegate = self;
[self addGestureRecognizer:longPressGestureRecognizer];
// We want to be permissive about taps on the send button, so we:
//
// * Add the gesture recognizer to the button's superview instead of the button.
// * Filter the touches that the gesture recognizer receives by serving as its
// delegate.
UITapGestureRecognizer *tapGestureRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
tapGestureRecognizer.delegate = self;
[self addGestureRecognizer:tapGestureRecognizer];
self.userInteractionEnabled = YES;
self.voiceMemoButton = button;
}
[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.shouldShowVoiceMemoButton) {
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) {
// Cancel voice message if necessary.
self.isRecordingVoiceMemo = NO;
[self.voiceMemoGestureDelegate voiceMemoGestureDidCancel];
}
break;
case UIGestureRecognizerStateBegan:
if (self.isRecordingVoiceMemo) {
// Cancel voice message if necessary.
self.isRecordingVoiceMemo = NO;
[self.voiceMemoGestureDelegate voiceMemoGestureDidCancel];
}
// Start voice message.
self.isRecordingVoiceMemo = YES;
self.voiceMemoGestureStartLocation = [sender locationInView:self];
[self.voiceMemoGestureDelegate voiceMemoGestureDidStart];
break;
case UIGestureRecognizerStateChanged:
if (self.isRecordingVoiceMemo) {
// Check for "slide to cancel" gesture.
CGPoint location = [sender locationInView:self];
CGFloat offset = MAX(0, self.voiceMemoGestureStartLocation.x - location.x);
// The lower this value, the easier it is to cancel by accident.
// The higher this value, the harder it is to cancel.
const CGFloat kCancelOffsetPoints = 100.f;
CGFloat cancelAlpha = offset / kCancelOffsetPoints;
BOOL isCancelled = cancelAlpha >= 1.f;
if (isCancelled) {
self.isRecordingVoiceMemo = NO;
[self.voiceMemoGestureDelegate voiceMemoGestureDidCancel];
} else {
[self.voiceMemoGestureDelegate voiceMemoGestureDidChange:cancelAlpha];
}
}
break;
case UIGestureRecognizerStateEnded:
if (self.isRecordingVoiceMemo) {
// End voice message.
self.isRecordingVoiceMemo = NO;
[self.voiceMemoGestureDelegate voiceMemoGestureDidEnd];
}
break;
}
}
- (void)handleTap:(UIGestureRecognizer *)sender
{
switch (sender.state) {
case UIGestureRecognizerStateRecognized:
[self.sendMessageGestureDelegate sendMessageGestureRecognized];
break;
default:
break;
}
}
- (void)cancelVoiceMemoIfNecessary
{
if (self.isRecordingVoiceMemo) {
self.isRecordingVoiceMemo = NO;
}
}
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
if (self.rightBarButtonItem != self.voiceMemoButton) {
return NO;
}
// We want to be permissive about the voice message gesture, so we accept
// gesture that begin within N points of its bounds.
CGFloat kVoiceMemoGestureTolerancePoints = 10;
CGPoint location = [touch locationInView:self.voiceMemoButton];
CGRect hitTestRect = CGRectInset(
self.voiceMemoButton.bounds, -kVoiceMemoGestureTolerancePoints, -kVoiceMemoGestureTolerancePoints);
return CGRectContainsPoint(hitTestRect, location);
} else if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
if (self.rightBarButtonItem == self.voiceMemoButton) {
return NO;
}
UIView *sendButton = self.rightBarButtonItem;
// We want to be permissive about taps on the send button, so we accept
// gesture that begin within N points of its bounds.
CGFloat kSendButtonTolerancePoints = 10;
CGPoint location = [touch locationInView:sendButton];
CGRect hitTestRect = CGRectInset(sendButton.bounds, -kSendButtonTolerancePoints, -kSendButtonTolerancePoints);
return CGRectContainsPoint(hitTestRect, location);
} else {
return YES;
}
}
@end