|
|
|
@ -50,7 +50,7 @@ static const CGFloat kBubbleTextVInset = 6.f;
|
|
|
|
|
|
|
|
|
|
//@property (nonatomic) BOOL isOutgoing;
|
|
|
|
|
@property (nonatomic) CAShapeLayer *shapeLayer;
|
|
|
|
|
@property (nonatomic) UIColor *bubbleColor;
|
|
|
|
|
@property (nonatomic, nullable) UIColor *bubbleColor;
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
@ -90,7 +90,7 @@ static const CGFloat kBubbleTextVInset = 6.f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)setBubbleColor:(UIColor *)bubbleColor
|
|
|
|
|
- (void)setBubbleColor:(nullable UIColor *)bubbleColor
|
|
|
|
|
{
|
|
|
|
|
_bubbleColor = bubbleColor;
|
|
|
|
|
|
|
|
|
@ -121,97 +121,6 @@ static const CGFloat kBubbleTextVInset = 6.f;
|
|
|
|
|
self.maskLayer.path = bezierPath.CGPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//- (void)updateMask
|
|
|
|
|
//{
|
|
|
|
|
// UIView *_Nullable maskedSubview = self.maskedSubview;
|
|
|
|
|
// if (!maskedSubview) {
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
// maskedSubview.frame = self.bounds;
|
|
|
|
|
// //<<<<<<< HEAD
|
|
|
|
|
// // // The JSQ masks are not RTL-safe, so we need to invert the
|
|
|
|
|
// // // mask orientation manually.
|
|
|
|
|
// // BOOL hasOutgoingMask = self.isOutgoing ^ self.isRTL;
|
|
|
|
|
// //
|
|
|
|
|
// // // Since the caption has it's own tail, the media bubble just above
|
|
|
|
|
// // // it looks better without a tail.
|
|
|
|
|
// // if (self.hideTail) {
|
|
|
|
|
// // if (hasOutgoingMask) {
|
|
|
|
|
// // self.layoutMargins = UIEdgeInsetsMake(0, 0, 2, 8);
|
|
|
|
|
// // } else {
|
|
|
|
|
// // self.layoutMargins = UIEdgeInsetsMake(0, 8, 2, 0);
|
|
|
|
|
// // }
|
|
|
|
|
// // maskedSubview.clipsToBounds = YES;
|
|
|
|
|
// //
|
|
|
|
|
// // // I arrived at this cornerRadius by superimposing the generated corner
|
|
|
|
|
// // // over that generated from the JSQMessagesMediaViewBubbleImageMasker
|
|
|
|
|
// // maskedSubview.layer.cornerRadius = 17;
|
|
|
|
|
// // } else {
|
|
|
|
|
// // [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:maskedSubview
|
|
|
|
|
// // isOutgoing:hasOutgoingMask];
|
|
|
|
|
// // }
|
|
|
|
|
// //||||||| merged common ancestors
|
|
|
|
|
// // // The JSQ masks are not RTL-safe, so we need to invert the
|
|
|
|
|
// // // mask orientation manually.
|
|
|
|
|
// // BOOL hasOutgoingMask = self.isOutgoing ^ self.isRTL;
|
|
|
|
|
// // [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:maskedSubview
|
|
|
|
|
// isOutgoing:hasOutgoingMask];
|
|
|
|
|
// //=======
|
|
|
|
|
//
|
|
|
|
|
//
|
|
|
|
|
// UIBezierPath *bezierPath = [BubbleFillView maskPathForSize:self.bounds.size
|
|
|
|
|
// isOutgoing:self.isOutgoing
|
|
|
|
|
// isRTL:self.isRTL];
|
|
|
|
|
// self.maskLayer.path = bezierPath.CGPath;
|
|
|
|
|
// maskedSubview.layer.mask = self.maskLayer;
|
|
|
|
|
// //>>>>>>> SQUASHED
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
//- (void)setIsOutgoing:(BOOL)isOutgoing {
|
|
|
|
|
// if (_isOutgoing == isOutgoing) {
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
// _isOutgoing = isOutgoing;
|
|
|
|
|
// [self updateMask];
|
|
|
|
|
//}
|
|
|
|
|
//
|
|
|
|
|
//- (void)setFrame:(CGRect)frame
|
|
|
|
|
//{
|
|
|
|
|
// BOOL didSizeChange = !CGSizeEqualToSize(self.frame.size, frame.size);
|
|
|
|
|
//
|
|
|
|
|
// [super setFrame:frame];
|
|
|
|
|
//
|
|
|
|
|
// if (didSizeChange || !self.shapeLayer) {
|
|
|
|
|
// [self updateMask];
|
|
|
|
|
// }
|
|
|
|
|
//}
|
|
|
|
|
//
|
|
|
|
|
//- (void)setBounds:(CGRect)bounds
|
|
|
|
|
//{
|
|
|
|
|
// BOOL didSizeChange = !CGSizeEqualToSize(self.bounds.size, bounds.size);
|
|
|
|
|
//
|
|
|
|
|
// [super setBounds:bounds];
|
|
|
|
|
//
|
|
|
|
|
// if (didSizeChange || !self.shapeLayer) {
|
|
|
|
|
// [self updateMask];
|
|
|
|
|
// }
|
|
|
|
|
//}
|
|
|
|
|
//
|
|
|
|
|
//- (void)updateMask
|
|
|
|
|
//{
|
|
|
|
|
// if (!self.shapeLayer) {
|
|
|
|
|
// self.shapeLayer = [CAShapeLayer new];
|
|
|
|
|
// [self.layer addSublayer:self.shapeLayer];
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// UIBezierPath *bezierPath = [self.class maskPathForSize:self.bounds.size
|
|
|
|
|
// isOutgoing:self.isOutgoing
|
|
|
|
|
// isRTL:self.isRTL];
|
|
|
|
|
//
|
|
|
|
|
// self.shapeLayer.fillColor = self.bubbleColor.CGColor;
|
|
|
|
|
// self.shapeLayer.path = bezierPath.CGPath;
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
+ (UIBezierPath *)maskPathForSize:(CGSize)size
|
|
|
|
|
isOutgoing:(BOOL)isOutgoing
|
|
|
|
|
isRTL:(BOOL)isRTL
|
|
|
|
@ -311,187 +220,10 @@ static const CGFloat kBubbleTextVInset = 6.f;
|
|
|
|
|
return bezierPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//- (void)setBubbleColor:(UIColor *)bubbleColor {
|
|
|
|
|
// _bubbleColor = bubbleColor;
|
|
|
|
|
//
|
|
|
|
|
// self.shapeLayer.fillColor = bubbleColor.CGColor;
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
//@interface BubbleFillView : UIView
|
|
|
|
|
//
|
|
|
|
|
////@property (nonatomic) BOOL isOutgoing;
|
|
|
|
|
////@property (nonatomic) CAShapeLayer *shapeLayer;
|
|
|
|
|
////@property (nonatomic) UIColor *bubbleColor;
|
|
|
|
|
//
|
|
|
|
|
//@end
|
|
|
|
|
//
|
|
|
|
|
//#pragma mark -
|
|
|
|
|
//
|
|
|
|
|
//@implementation BubbleFillView
|
|
|
|
|
//
|
|
|
|
|
////- (void)setIsOutgoing:(BOOL)isOutgoing {
|
|
|
|
|
//// if (_isOutgoing == isOutgoing) {
|
|
|
|
|
//// return;
|
|
|
|
|
//// }
|
|
|
|
|
//// _isOutgoing = isOutgoing;
|
|
|
|
|
//// [self updateMask];
|
|
|
|
|
////}
|
|
|
|
|
////
|
|
|
|
|
////- (void)setFrame:(CGRect)frame
|
|
|
|
|
////{
|
|
|
|
|
//// BOOL didSizeChange = !CGSizeEqualToSize(self.frame.size, frame.size);
|
|
|
|
|
////
|
|
|
|
|
//// [super setFrame:frame];
|
|
|
|
|
////
|
|
|
|
|
//// if (didSizeChange || !self.shapeLayer) {
|
|
|
|
|
//// [self updateMask];
|
|
|
|
|
//// }
|
|
|
|
|
////}
|
|
|
|
|
////
|
|
|
|
|
////- (void)setBounds:(CGRect)bounds
|
|
|
|
|
////{
|
|
|
|
|
//// BOOL didSizeChange = !CGSizeEqualToSize(self.bounds.size, bounds.size);
|
|
|
|
|
////
|
|
|
|
|
//// [super setBounds:bounds];
|
|
|
|
|
////
|
|
|
|
|
//// if (didSizeChange || !self.shapeLayer) {
|
|
|
|
|
//// [self updateMask];
|
|
|
|
|
//// }
|
|
|
|
|
////}
|
|
|
|
|
////
|
|
|
|
|
////- (void)updateMask
|
|
|
|
|
////{
|
|
|
|
|
//// if (!self.shapeLayer) {
|
|
|
|
|
//// self.shapeLayer = [CAShapeLayer new];
|
|
|
|
|
//// [self.layer addSublayer:self.shapeLayer];
|
|
|
|
|
//// }
|
|
|
|
|
////
|
|
|
|
|
//// UIBezierPath *bezierPath = [self.class maskPathForSize:self.bounds.size
|
|
|
|
|
//// isOutgoing:self.isOutgoing
|
|
|
|
|
//// isRTL:self.isRTL];
|
|
|
|
|
////
|
|
|
|
|
//// self.shapeLayer.fillColor = self.bubbleColor.CGColor;
|
|
|
|
|
//// self.shapeLayer.path = bezierPath.CGPath;
|
|
|
|
|
////}
|
|
|
|
|
//
|
|
|
|
|
////- (void)setBubbleColor:(UIColor *)bubbleColor {
|
|
|
|
|
//// _bubbleColor = bubbleColor;
|
|
|
|
|
////
|
|
|
|
|
//// self.shapeLayer.fillColor = bubbleColor.CGColor;
|
|
|
|
|
////}
|
|
|
|
|
//
|
|
|
|
|
//@end
|
|
|
|
|
//
|
|
|
|
|
//#pragma mark -
|
|
|
|
|
//
|
|
|
|
|
//@interface BubbleMaskingView : UIView
|
|
|
|
|
//
|
|
|
|
|
//@property (nonatomic) BOOL isOutgoing;
|
|
|
|
|
//@property (nonatomic) BOOL hideTail;
|
|
|
|
|
//@property (nonatomic, nullable, weak) UIView *maskedSubview;
|
|
|
|
|
//@property (nonatomic) CAShapeLayer *maskLayer;
|
|
|
|
|
//
|
|
|
|
|
//@end
|
|
|
|
|
//
|
|
|
|
|
//#pragma mark -
|
|
|
|
|
//
|
|
|
|
|
//@implementation BubbleMaskingView
|
|
|
|
|
//
|
|
|
|
|
//- (void)setMaskedSubview:(UIView * _Nullable)maskedSubview {
|
|
|
|
|
// if (_maskedSubview == maskedSubview) {
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
// _maskedSubview = maskedSubview;
|
|
|
|
|
// [self updateMask];
|
|
|
|
|
//}
|
|
|
|
|
//
|
|
|
|
|
//- (void)setIsOutgoing:(BOOL)isOutgoing {
|
|
|
|
|
// if (_isOutgoing == isOutgoing) {
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
// _isOutgoing = isOutgoing;
|
|
|
|
|
// [self updateMask];
|
|
|
|
|
//}
|
|
|
|
|
//
|
|
|
|
|
//- (void)setFrame:(CGRect)frame
|
|
|
|
|
//{
|
|
|
|
|
// BOOL didSizeChange = !CGSizeEqualToSize(self.frame.size, frame.size);
|
|
|
|
|
//
|
|
|
|
|
// [super setFrame:frame];
|
|
|
|
|
//
|
|
|
|
|
// if (didSizeChange) {
|
|
|
|
|
// [self updateMask];
|
|
|
|
|
// }
|
|
|
|
|
//}
|
|
|
|
|
//
|
|
|
|
|
//- (void)setBounds:(CGRect)bounds
|
|
|
|
|
//{
|
|
|
|
|
// BOOL didSizeChange = !CGSizeEqualToSize(self.bounds.size, bounds.size);
|
|
|
|
|
//
|
|
|
|
|
// [super setBounds:bounds];
|
|
|
|
|
//
|
|
|
|
|
// if (didSizeChange) {
|
|
|
|
|
// [self updateMask];
|
|
|
|
|
// }
|
|
|
|
|
//}
|
|
|
|
|
//
|
|
|
|
|
//- (void)updateMask
|
|
|
|
|
//{
|
|
|
|
|
// UIView *_Nullable maskedSubview = self.maskedSubview;
|
|
|
|
|
// if (!maskedSubview) {
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
// maskedSubview.frame = self.bounds;
|
|
|
|
|
// //<<<<<<< HEAD
|
|
|
|
|
// // // The JSQ masks are not RTL-safe, so we need to invert the
|
|
|
|
|
// // // mask orientation manually.
|
|
|
|
|
// // BOOL hasOutgoingMask = self.isOutgoing ^ self.isRTL;
|
|
|
|
|
// //
|
|
|
|
|
// // // Since the caption has it's own tail, the media bubble just above
|
|
|
|
|
// // // it looks better without a tail.
|
|
|
|
|
// // if (self.hideTail) {
|
|
|
|
|
// // if (hasOutgoingMask) {
|
|
|
|
|
// // self.layoutMargins = UIEdgeInsetsMake(0, 0, 2, 8);
|
|
|
|
|
// // } else {
|
|
|
|
|
// // self.layoutMargins = UIEdgeInsetsMake(0, 8, 2, 0);
|
|
|
|
|
// // }
|
|
|
|
|
// // maskedSubview.clipsToBounds = YES;
|
|
|
|
|
// //
|
|
|
|
|
// // // I arrived at this cornerRadius by superimposing the generated corner
|
|
|
|
|
// // // over that generated from the JSQMessagesMediaViewBubbleImageMasker
|
|
|
|
|
// // maskedSubview.layer.cornerRadius = 17;
|
|
|
|
|
// // } else {
|
|
|
|
|
// // [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:maskedSubview
|
|
|
|
|
// // isOutgoing:hasOutgoingMask];
|
|
|
|
|
// // }
|
|
|
|
|
// //||||||| merged common ancestors
|
|
|
|
|
// // // The JSQ masks are not RTL-safe, so we need to invert the
|
|
|
|
|
// // // mask orientation manually.
|
|
|
|
|
// // BOOL hasOutgoingMask = self.isOutgoing ^ self.isRTL;
|
|
|
|
|
// // [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:maskedSubview
|
|
|
|
|
// isOutgoing:hasOutgoingMask];
|
|
|
|
|
// //=======
|
|
|
|
|
//
|
|
|
|
|
// if (!self.maskLayer) {
|
|
|
|
|
// self.maskLayer = [CAShapeLayer new];
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// UIBezierPath *bezierPath = [OWSBubbleView maskPathForSize:self.bounds.size
|
|
|
|
|
// isOutgoing:self.isOutgoing
|
|
|
|
|
// isRTL:self.isRTL];
|
|
|
|
|
// self.maskLayer.path = bezierPath.CGPath;
|
|
|
|
|
// maskedSubview.layer.mask = self.maskLayer;
|
|
|
|
|
// //>>>>>>> SQUASHED
|
|
|
|
|
//}
|
|
|
|
|
//
|
|
|
|
|
//@end
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
@interface OWSMessageTextView : UITextView
|
|
|
|
|
|
|
|
|
|
@property (nonatomic) BOOL shouldIgnoreEvents;
|
|
|
|
@ -602,7 +334,7 @@ static const CGFloat kBubbleTextVInset = 6.f;
|
|
|
|
|
@property (nonatomic) UILabel *footerLabel;
|
|
|
|
|
@property (nonatomic, nullable) OWSExpirationTimerView *expirationTimerView;
|
|
|
|
|
|
|
|
|
|
@property (nonatomic, nullable) UIImageView *lastImageView;
|
|
|
|
|
@property (nonatomic, nullable) UIView *lastBodyMediaView;
|
|
|
|
|
|
|
|
|
|
// Should lazy-load expensive view contents (images, etc.).
|
|
|
|
|
// Should do nothing if view is already loaded.
|
|
|
|
@ -743,6 +475,22 @@ static const CGFloat kBubbleTextVInset = 6.f;
|
|
|
|
|
[self.footerView autoPinEdgeToSuperviewEdge:ALEdgeBottom];
|
|
|
|
|
[self.footerView autoPinWidthToSuperview];
|
|
|
|
|
|
|
|
|
|
self.bubbleView.userInteractionEnabled = YES;
|
|
|
|
|
|
|
|
|
|
UITapGestureRecognizer *tap =
|
|
|
|
|
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
|
|
|
|
|
[self.bubbleView addGestureRecognizer:tap];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
UILongPressGestureRecognizer *longPress =
|
|
|
|
|
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)];
|
|
|
|
|
[self.bubbleView addGestureRecognizer:longPress];
|
|
|
|
|
|
|
|
|
|
PanDirectionGestureRecognizer *panGesture = [[PanDirectionGestureRecognizer alloc]
|
|
|
|
|
initWithDirection:(self.isRTL ? PanDirectionLeft : PanDirectionRight)target:self
|
|
|
|
|
action:@selector(handlePanGesture:)];
|
|
|
|
|
[self addGestureRecognizer:panGesture];
|
|
|
|
|
|
|
|
|
|
// UITapGestureRecognizer *mediaTap =
|
|
|
|
|
// [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleMediaTapGesture:)];
|
|
|
|
|
// [self.mediaMaskingView addGestureRecognizer:mediaTap];
|
|
|
|
@ -972,7 +720,8 @@ static const CGFloat kBubbleTextVInset = 6.f;
|
|
|
|
|
self.bubbleView.bubbleColor = [self.bubbleFactory bubbleColorWithMessage:message];
|
|
|
|
|
// self.bubbleFillView.bubbleColor = [self.bubbleFactory bubbleColorWithMessage:message];
|
|
|
|
|
} else {
|
|
|
|
|
OWSFail(@"%@ Unknown interaction type: %@", self.logTag, self.viewItem.interaction.class);
|
|
|
|
|
// Media-only messages should have no background color; they will fill the bubble's bounds
|
|
|
|
|
// and we don't want artifacts at the edges.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//<<<<<<< HEAD
|
|
|
|
@ -1104,6 +853,7 @@ static const CGFloat kBubbleTextVInset = 6.f;
|
|
|
|
|
OWSAssert(self.unloadCellContentBlock);
|
|
|
|
|
OWSAssert(!lastSubview);
|
|
|
|
|
|
|
|
|
|
self.lastBodyMediaView = bodyMediaView;
|
|
|
|
|
bodyMediaView.userInteractionEnabled = NO;
|
|
|
|
|
if (self.isMediaBeingSent) {
|
|
|
|
|
bodyMediaView.layer.opacity = 0.75f;
|
|
|
|
@ -1750,7 +1500,6 @@ static const CGFloat kBubbleTextVInset = 6.f;
|
|
|
|
|
self.unloadCellContentBlock = ^{
|
|
|
|
|
stillImageView.image = nil;
|
|
|
|
|
};
|
|
|
|
|
self.lastImageView = stillImageView;
|
|
|
|
|
|
|
|
|
|
return stillImageView;
|
|
|
|
|
}
|
|
|
|
@ -1793,7 +1542,6 @@ static const CGFloat kBubbleTextVInset = 6.f;
|
|
|
|
|
self.unloadCellContentBlock = ^{
|
|
|
|
|
animatedImageView.image = nil;
|
|
|
|
|
};
|
|
|
|
|
self.lastImageView = animatedImageView;
|
|
|
|
|
|
|
|
|
|
return animatedImageView;
|
|
|
|
|
}
|
|
|
|
@ -1878,7 +1626,6 @@ static const CGFloat kBubbleTextVInset = 6.f;
|
|
|
|
|
self.unloadCellContentBlock = ^{
|
|
|
|
|
stillImageView.image = nil;
|
|
|
|
|
};
|
|
|
|
|
self.lastImageView = stillImageView;
|
|
|
|
|
|
|
|
|
|
return stillImageView;
|
|
|
|
|
}
|
|
|
|
@ -2252,6 +1999,7 @@ static const CGFloat kBubbleTextVInset = 6.f;
|
|
|
|
|
|
|
|
|
|
// TODO:
|
|
|
|
|
self.bubbleView.hidden = YES;
|
|
|
|
|
self.bubbleView.bubbleColor = nil;
|
|
|
|
|
//<<<<<<< HEAD
|
|
|
|
|
// self.textBubbleImageView.image = nil;
|
|
|
|
|
// self.textBubbleImageView.hidden = YES;
|
|
|
|
@ -2297,8 +2045,8 @@ static const CGFloat kBubbleTextVInset = 6.f;
|
|
|
|
|
[self.expirationTimerView removeFromSuperview];
|
|
|
|
|
self.expirationTimerView = nil;
|
|
|
|
|
|
|
|
|
|
[self.lastImageView removeFromSuperview];
|
|
|
|
|
self.lastImageView = nil;
|
|
|
|
|
[self.lastBodyMediaView removeFromSuperview];
|
|
|
|
|
self.lastBodyMediaView = nil;
|
|
|
|
|
|
|
|
|
|
[self hideMenuControllerIfNecessary];
|
|
|
|
|
}
|
|
|
|
@ -2331,6 +2079,30 @@ static const CGFloat kBubbleTextVInset = 6.f;
|
|
|
|
|
|
|
|
|
|
#pragma mark - Gesture recognizers
|
|
|
|
|
|
|
|
|
|
- (void)handleTapGesture:(UITapGestureRecognizer *)sender
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(self.delegate);
|
|
|
|
|
|
|
|
|
|
if (sender.state != UIGestureRecognizerStateRecognized) {
|
|
|
|
|
DDLogVerbose(@"%@ Ignoring tap on message: %@", self.logTag, self.viewItem.interaction.debugDescription);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (self.lastBodyMediaView) {
|
|
|
|
|
// Treat this as a "body media" gesture if:
|
|
|
|
|
//
|
|
|
|
|
// * There is a "body media" view.
|
|
|
|
|
// * The gesture occured within or above the "body media" view.
|
|
|
|
|
CGPoint location = [sender locationInView:self.lastBodyMediaView];
|
|
|
|
|
if (location.y <= self.lastBodyMediaView.height) {
|
|
|
|
|
[self handleMediaTapGesture:sender];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[self handleTextTapGesture:sender];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)handleTextTapGesture:(UITapGestureRecognizer *)sender
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(self.delegate);
|
|
|
|
@ -2388,25 +2160,25 @@ static const CGFloat kBubbleTextVInset = 6.f;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case OWSMessageCellType_StillImage:
|
|
|
|
|
OWSAssert(self.lastImageView);
|
|
|
|
|
OWSAssert(self.lastBodyMediaView);
|
|
|
|
|
[self.delegate didTapImageViewItem:self.viewItem
|
|
|
|
|
attachmentStream:self.attachmentStream
|
|
|
|
|
imageView:self.lastImageView];
|
|
|
|
|
imageView:self.lastBodyMediaView];
|
|
|
|
|
break;
|
|
|
|
|
case OWSMessageCellType_AnimatedImage:
|
|
|
|
|
OWSAssert(self.lastImageView);
|
|
|
|
|
OWSAssert(self.lastBodyMediaView);
|
|
|
|
|
[self.delegate didTapImageViewItem:self.viewItem
|
|
|
|
|
attachmentStream:self.attachmentStream
|
|
|
|
|
imageView:self.lastImageView];
|
|
|
|
|
imageView:self.lastBodyMediaView];
|
|
|
|
|
break;
|
|
|
|
|
case OWSMessageCellType_Audio:
|
|
|
|
|
[self.delegate didTapAudioViewItem:self.viewItem attachmentStream:self.attachmentStream];
|
|
|
|
|
return;
|
|
|
|
|
case OWSMessageCellType_Video:
|
|
|
|
|
OWSAssert(self.lastImageView);
|
|
|
|
|
OWSAssert(self.lastBodyMediaView);
|
|
|
|
|
[self.delegate didTapVideoViewItem:self.viewItem
|
|
|
|
|
attachmentStream:self.attachmentStream
|
|
|
|
|
imageView:self.lastImageView];
|
|
|
|
|
imageView:self.lastBodyMediaView];
|
|
|
|
|
return;
|
|
|
|
|
case OWSMessageCellType_GenericAttachment:
|
|
|
|
|
[AttachmentSharing showShareUIForAttachment:self.attachmentStream];
|
|
|
|
@ -2421,6 +2193,29 @@ static const CGFloat kBubbleTextVInset = 6.f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)handleLongPressGesture:(UILongPressGestureRecognizer *)sender
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(self.delegate);
|
|
|
|
|
|
|
|
|
|
if (sender.state != UIGestureRecognizerStateBegan) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (self.lastBodyMediaView) {
|
|
|
|
|
// Treat this as a "body media" gesture if:
|
|
|
|
|
//
|
|
|
|
|
// * There is a "body media" view.
|
|
|
|
|
// * The gesture occured within or above the "body media" view.
|
|
|
|
|
CGPoint location = [sender locationInView:self.lastBodyMediaView];
|
|
|
|
|
if (location.y <= self.lastBodyMediaView.height) {
|
|
|
|
|
[self handleMediaLongPressGesture:sender];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[self handleTextLongPressGesture:sender];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)handleTextLongPressGesture:(UILongPressGestureRecognizer *)sender
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(self.delegate);
|
|
|
|
|