|
|
|
@ -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<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
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
@implementation OWSMessagesComposerTextView
|
|
|
|
|
|
|
|
|
|
- (instancetype)init
|
|
|
|
|
- (BOOL)canBecomeFirstResponder
|
|
|
|
|
{
|
|
|
|
|
self = [super init];
|
|
|
|
|
if (!self) {
|
|
|
|
|
return self;
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[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 (!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<UITextViewDelegate>)delegate
|
|
|
|
|
{
|
|
|
|
|
if (delegate == self) {
|
|
|
|
|
[super setDelegate:delegate];
|
|
|
|
|
} else {
|
|
|
|
|
self.jsqDelegate = delegate;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
#pragma mark - UITextViewDelegate
|
|
|
|
|
@protocol OWSMessagesToolbarContentDelegate <NSObject>
|
|
|
|
|
|
|
|
|
|
- (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<OWSMessagesToolbarContentDelegate> 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
|
|
|
|
|
|
|
|
|
|
- (BOOL)textView:(UITextView *)textView
|
|
|
|
|
shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment
|
|
|
|
|
inRange:(NSRange)characterRange
|
|
|
|
|
interaction:(UITextItemInteraction)interaction
|
|
|
|
|
#pragma mark - Class methods
|
|
|
|
|
|
|
|
|
|
+ (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;
|
|
|
|
|
if (!self.sendButton) {
|
|
|
|
|
OWSAssert(self.rightBarButtonItem);
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
self.sendButton = self.rightBarButtonItem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (self.shouldShowVoiceMemoButton) {
|
|
|
|
|
if (!self.voiceMemoButton) {
|
|
|
|
|
UIImage *icon = [UIImage imageNamed:@"voice-memo-button"];
|
|
|
|
|
OWSAssert(icon);
|
|
|
|
|
UIImageView *imageView = [[UIImageView alloc] initWithImage:icon];
|
|
|
|
|
imageView.layer.opacity = 0.8f;
|
|
|
|
|
[button addSubview:imageView];
|
|
|
|
|
|
|
|
|
|
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 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;
|
|
|
|
|
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 () <JSQMessagesComposerTextViewPasteDelegate,
|
|
|
|
|
OWSTextViewPasteDelegate,
|
|
|
|
|
OWSMessagesToolbarContentDelegate,
|
|
|
|
|
OWSConversationSettingsViewDelegate,
|
|
|
|
|
UIDocumentMenuDelegate,
|
|
|
|
|
UIDocumentPickerDelegate> {
|
|
|
|
@ -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
|
|
|
|
|