session-ios/Session/Conversations/Views & Cells/OWSMessageCell.m

524 lines
18 KiB
Mathematica
Raw Normal View History

2017-10-10 22:13:54 +02:00
//
2019-01-18 16:23:07 +01:00
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
2017-10-10 22:13:54 +02:00
//
#import "OWSMessageCell.h"
2018-04-05 19:26:15 +02:00
#import "OWSMessageBubbleView.h"
2018-07-11 20:12:58 +02:00
#import "OWSMessageHeaderView.h"
2019-05-02 23:58:48 +02:00
#import "Session-Swift.h"
2020-11-11 07:45:50 +01:00
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
2018-04-05 19:19:13 +02:00
2017-10-10 22:13:54 +02:00
NS_ASSUME_NONNULL_BEGIN
@interface OWSMessageCell () <UIGestureRecognizerDelegate>
2017-10-10 22:13:54 +02:00
2017-10-12 23:04:35 +02:00
// The nullable properties are created as needed.
// The non-nullable properties are so frequently used that it's easier
// to always keep one around.
2017-11-17 16:49:34 +01:00
2018-07-11 20:12:58 +02:00
@property (nonatomic) OWSMessageHeaderView *headerView;
2018-04-05 19:19:13 +02:00
@property (nonatomic) OWSMessageBubbleView *messageBubbleView;
2019-05-13 08:21:07 +02:00
@property (nonatomic) NSLayoutConstraint *messageBubbleViewBottomConstraint;
2019-12-10 04:27:38 +01:00
@property (nonatomic) LKProfilePictureView *avatarView;
@property (nonatomic) UIImageView *moderatorIconImageView;
2018-07-05 16:25:30 +02:00
@property (nonatomic, nullable) UIImageView *sendFailureBadgeView;
2017-11-17 16:49:34 +01:00
2018-03-28 16:11:01 +02:00
@property (nonatomic, nullable) NSMutableArray<NSLayoutConstraint *> *viewConstraints;
@property (nonatomic) BOOL isPresentingMenuController;
2017-10-10 22:13:54 +02:00
@end
2018-07-05 16:25:30 +02:00
#pragma mark -
2017-10-10 22:13:54 +02:00
@implementation OWSMessageCell
// `[UIView init]` invokes `[self initWithFrame:...]`.
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self commonInit];
2017-10-10 22:13:54 +02:00
}
return self;
}
- (void)commonInit
2017-10-10 22:13:54 +02:00
{
2018-04-13 21:10:16 +02:00
// Ensure only called once.
OWSAssertDebug(!self.messageBubbleView);
2017-10-10 22:13:54 +02:00
self.layoutMargins = UIEdgeInsetsZero;
self.contentView.layoutMargins = UIEdgeInsetsZero;
2017-10-10 22:13:54 +02:00
2018-06-22 20:53:33 +02:00
_viewConstraints = [NSMutableArray new];
2018-04-05 19:19:13 +02:00
self.messageBubbleView = [OWSMessageBubbleView new];
[self.contentView addSubview:self.messageBubbleView];
2017-11-17 16:49:34 +01:00
2018-07-11 20:12:58 +02:00
self.headerView = [OWSMessageHeaderView new];
2019-12-10 04:27:38 +01:00
self.avatarView = [[LKProfilePictureView alloc] init];
2018-06-25 20:31:09 +02:00
[self.avatarView autoSetDimension:ALDimensionWidth toSize:self.avatarSize];
[self.avatarView autoSetDimension:ALDimensionHeight toSize:self.avatarSize];
self.moderatorIconImageView = [[UIImageView alloc] init];
[self.moderatorIconImageView autoSetDimension:ALDimensionWidth toSize:20.f];
[self.moderatorIconImageView autoSetDimension:ALDimensionHeight toSize:20.f];
self.moderatorIconImageView.hidden = YES;
2019-09-12 07:06:20 +02:00
2019-05-13 08:21:07 +02:00
self.messageBubbleViewBottomConstraint = [self.messageBubbleView autoPinBottomToSuperviewMarginWithInset:0];
2017-10-10 22:13:54 +02:00
2018-03-28 20:46:16 +02:00
self.contentView.userInteractionEnabled = YES;
2018-03-27 19:25:02 +02:00
2018-04-17 19:28:06 +02:00
UITapGestureRecognizer *tap =
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
[self addGestureRecognizer:tap];
2018-03-27 19:25:02 +02:00
UILongPressGestureRecognizer *longPress =
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)];
2018-03-28 20:46:16 +02:00
[self.contentView addGestureRecognizer:longPress];
2020-10-19 02:21:04 +02:00
UIPanGestureRecognizer *pan =
[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
pan.delegate = self;
2020-10-19 02:21:04 +02:00
[self.contentView addGestureRecognizer:pan];
2017-11-17 16:49:34 +01:00
}
2018-06-25 20:31:09 +02:00
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
2018-06-25 21:20:17 +02:00
- (void)setConversationStyle:(nullable ConversationStyle *)conversationStyle
2018-06-22 19:48:23 +02:00
{
2018-06-25 21:20:17 +02:00
[super setConversationStyle:conversationStyle];
2018-06-22 19:48:23 +02:00
2018-06-25 21:20:17 +02:00
self.messageBubbleView.conversationStyle = conversationStyle;
2018-06-22 19:48:23 +02:00
}
2017-10-17 06:05:29 +02:00
+ (NSString *)cellReuseIdentifier
{
return NSStringFromClass([self class]);
}
2018-04-05 19:19:13 +02:00
#pragma mark - Convenience Accessors
2017-10-10 22:13:54 +02:00
- (OWSMessageCellType)cellType
{
return self.viewItem.messageCellType;
}
2017-10-17 06:05:29 +02:00
- (TSMessage *)message
{
OWSAssertDebug([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
2017-10-17 06:05:29 +02:00
return (TSMessage *)self.viewItem.interaction;
}
2018-04-05 19:19:13 +02:00
- (BOOL)isIncoming
{
return self.viewItem.interaction.interactionType == OWSInteractionType_IncomingMessage;
}
- (BOOL)isOutgoing
{
return self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage;
}
2018-07-05 23:27:37 +02:00
- (BOOL)shouldHaveSendFailureBadge
2018-07-05 16:25:30 +02:00
{
if (![self.viewItem.interaction isKindOfClass:[TSOutgoingMessage class]]) {
return NO;
}
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction;
return outgoingMessage.messageState == TSOutgoingMessageStateFailed;
}
2017-11-17 16:49:34 +01:00
#pragma mark - Load
2018-08-09 16:47:43 +02:00
- (void)loadForDisplay
2017-10-10 22:13:54 +02:00
{
OWSAssertDebug(self.conversationStyle);
OWSAssertDebug(self.viewItem);
OWSAssertDebug(self.viewItem.interaction);
OWSAssertDebug([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
OWSAssertDebug(self.messageBubbleView);
2018-03-28 19:34:33 +02:00
2019-05-13 08:21:07 +02:00
[self.messageBubbleViewBottomConstraint setActive:YES];
2018-04-05 19:19:13 +02:00
self.messageBubbleView.viewItem = self.viewItem;
self.messageBubbleView.cellMediaCache = self.delegate.cellMediaCache;
[self.messageBubbleView configureViews];
[self.messageBubbleView loadContent];
2018-03-28 16:01:01 +02:00
2018-07-11 20:12:58 +02:00
if (self.viewItem.hasCellHeader) {
CGFloat headerHeight =
[self.headerView measureWithConversationViewItem:self.viewItem conversationStyle:self.conversationStyle]
.height;
[self.headerView loadForDisplayWithViewItem:self.viewItem conversationStyle:self.conversationStyle];
[self.contentView addSubview:self.headerView];
[self.viewConstraints addObjectsFromArray:@[
[self.headerView autoSetDimension:ALDimensionHeight toSize:headerHeight],
[self.headerView autoPinEdgeToSuperviewEdge:ALEdgeLeading],
[self.headerView autoPinEdgeToSuperviewEdge:ALEdgeTrailing],
2018-07-11 20:12:58 +02:00
[self.headerView autoPinEdgeToSuperviewEdge:ALEdgeTop],
[self.messageBubbleView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.headerView],
]];
} else {
[self.viewConstraints addObjectsFromArray:@[
[self.messageBubbleView autoPinEdgeToSuperviewEdge:ALEdgeTop],
]];
}
2018-04-16 17:46:20 +02:00
2018-06-22 20:53:33 +02:00
if (self.isIncoming) {
2018-03-28 16:11:01 +02:00
[self.viewConstraints addObjectsFromArray:@[
2018-06-25 21:20:17 +02:00
[self.messageBubbleView autoPinEdgeToSuperviewEdge:ALEdgeLeading
withInset:self.conversationStyle.gutterLeading],
2018-06-22 20:53:33 +02:00
[self.messageBubbleView autoPinEdgeToSuperviewEdge:ALEdgeTrailing
2018-06-25 21:20:17 +02:00
withInset:self.conversationStyle.gutterTrailing
2018-06-22 20:53:33 +02:00
relation:NSLayoutRelationGreaterThanOrEqual],
2018-03-28 16:11:01 +02:00
]];
} else {
2018-07-05 23:27:37 +02:00
if (self.shouldHaveSendFailureBadge) {
2018-07-05 16:25:30 +02:00
self.sendFailureBadgeView = [UIImageView new];
self.sendFailureBadgeView.image =
[self.sendFailureBadge imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
2019-12-10 04:27:38 +01:00
self.sendFailureBadgeView.tintColor = LKColors.destructive;
2018-07-05 16:25:30 +02:00
[self.contentView addSubview:self.sendFailureBadgeView];
CGFloat sendFailureBadgeBottomMargin
= round(self.conversationStyle.lastTextLineAxis - self.sendFailureBadgeSize * 0.5f);
[self.viewConstraints addObjectsFromArray:@[
[self.messageBubbleView autoPinEdgeToSuperviewEdge:ALEdgeLeading
withInset:self.conversationStyle.gutterLeading
relation:NSLayoutRelationGreaterThanOrEqual],
[self.sendFailureBadgeView autoPinLeadingToTrailingEdgeOfView:self.messageBubbleView
offset:self.sendFailureBadgeSpacing],
// V-align the "send failure" badge with the
// last line of the text (if any, or where it
// would be).
[self.messageBubbleView autoPinEdge:ALEdgeBottom
toEdge:ALEdgeBottom
ofView:self.sendFailureBadgeView
withOffset:sendFailureBadgeBottomMargin],
[self.sendFailureBadgeView autoPinEdgeToSuperviewEdge:ALEdgeTrailing
2018-07-05 16:28:47 +02:00
withInset:self.conversationStyle.errorGutterTrailing],
2018-07-05 16:25:30 +02:00
[self.sendFailureBadgeView autoSetDimension:ALDimensionWidth toSize:self.sendFailureBadgeSize],
[self.sendFailureBadgeView autoSetDimension:ALDimensionHeight toSize:self.sendFailureBadgeSize],
]];
} else {
[self.viewConstraints addObjectsFromArray:@[
[self.messageBubbleView autoPinEdgeToSuperviewEdge:ALEdgeLeading
withInset:self.conversationStyle.gutterLeading
relation:NSLayoutRelationGreaterThanOrEqual],
[self.messageBubbleView autoPinEdgeToSuperviewEdge:ALEdgeTrailing
withInset:self.conversationStyle.gutterTrailing],
]];
}
}
2018-06-25 20:31:09 +02:00
if ([self updateAvatarView]) {
[self.viewConstraints addObjectsFromArray:@[
2020-03-10 05:02:52 +01:00
[self.messageBubbleView autoPinLeadingToTrailingEdgeOfView:self.avatarView offset:12],
[self.messageBubbleView autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self.avatarView],
2018-06-25 20:31:09 +02:00
]];
2019-09-12 07:06:20 +02:00
[self.viewConstraints addObjectsFromArray:@[
[self.moderatorIconImageView autoPinEdge:ALEdgeTrailing toEdge:ALEdgeTrailing ofView:self.avatarView],
[self.moderatorIconImageView autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:self.avatarView withOffset:3.5]
2019-09-12 07:06:20 +02:00
]];
2018-06-25 20:31:09 +02:00
}
}
2018-07-05 16:25:30 +02:00
- (UIImage *)sendFailureBadge
{
UIImage *image = [UIImage imageNamed:@"message_status_failed_large"];
OWSAssertDebug(image);
OWSAssertDebug(image.size.width == self.sendFailureBadgeSize && image.size.height == self.sendFailureBadgeSize);
2018-07-05 16:25:30 +02:00
return image;
}
- (CGFloat)sendFailureBadgeSize
{
return 20.f;
}
- (CGFloat)sendFailureBadgeSpacing
{
return 8.f;
}
2017-11-17 16:49:34 +01:00
// * If cell is visible, lazy-load (expensive) view contents.
// * If cell is not visible, eagerly unload view contents.
- (void)ensureMediaLoadState
{
OWSAssertDebug(self.messageBubbleView);
2018-04-05 19:19:13 +02:00
if (!self.isCellVisible) {
2018-04-05 19:19:13 +02:00
[self.messageBubbleView unloadContent];
2017-11-17 16:49:34 +01:00
} else {
2018-04-05 19:19:13 +02:00
[self.messageBubbleView loadContent];
}
}
2018-06-25 20:31:09 +02:00
#pragma mark - Avatar
// Returns YES IFF the avatar view is appropriate and configured.
- (BOOL)updateAvatarView
{
2021-01-29 01:46:32 +01:00
if (!self.viewItem.shouldShowSenderProfilePicture) {
2018-06-25 20:31:09 +02:00
return NO;
}
2018-06-26 22:04:09 +02:00
if (!self.viewItem.isGroupThread) {
OWSFailDebug(@"not a group thread.");
2018-06-25 20:31:09 +02:00
return NO;
}
2018-06-26 22:04:09 +02:00
if (self.viewItem.interaction.interactionType != OWSInteractionType_IncomingMessage) {
OWSFailDebug(@"not an incoming message.");
2018-06-25 20:31:09 +02:00
return NO;
}
2018-09-26 15:19:12 +02:00
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)self.viewItem.interaction;
2019-12-10 04:27:38 +01:00
[self.contentView addSubview:self.avatarView];
2019-12-10 04:27:38 +01:00
self.avatarView.size = self.avatarSize;
self.avatarView.hexEncodedPublicKey = incomingMessage.authorId;
[self.avatarView update];
2019-09-12 07:06:20 +02:00
// Loki: Show the moderator icon if needed
2020-11-16 00:34:47 +01:00
if (self.viewItem.isGroupThread) { // FIXME: This logic also shouldn't apply to closed groups
2020-11-19 05:24:09 +01:00
SNOpenGroup *publicChat = [LKStorage.shared getOpenGroupForThreadID:self.viewItem.interaction.uniqueThreadId];
2019-10-15 01:29:41 +02:00
if (publicChat != nil) {
2020-11-11 07:45:50 +01:00
BOOL isModerator = [SNOpenGroupAPI isUserModerator:incomingMessage.authorId forChannel:publicChat.channel onServer:publicChat.server];
2019-10-15 01:29:41 +02:00
UIImage *moderatorIcon = [UIImage imageNamed:@"Crown"];
self.moderatorIconImageView.image = moderatorIcon;
self.moderatorIconImageView.hidden = !isModerator;
}
2019-09-12 07:06:20 +02:00
}
[self.contentView addSubview:self.moderatorIconImageView];
2018-06-25 20:31:09 +02:00
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(otherUsersProfileDidChange:)
name:kNSNotificationName_OtherUsersProfileDidChange
object:nil];
return YES;
}
2019-12-10 04:27:38 +01:00
- (CGFloat)avatarSize
2018-06-25 20:31:09 +02:00
{
2019-12-10 04:27:38 +01:00
return LKValues.smallProfilePictureSize;
2018-06-25 20:31:09 +02:00
}
- (void)otherUsersProfileDidChange:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
2021-01-29 01:46:32 +01:00
if (!self.viewItem.shouldShowSenderProfilePicture) {
2018-06-25 20:31:09 +02:00
return;
}
2018-06-26 22:04:09 +02:00
if (!self.viewItem.isGroupThread) {
OWSFailDebug(@"not a group thread.");
2018-06-25 20:31:09 +02:00
return;
}
2018-06-26 22:04:09 +02:00
if (self.viewItem.interaction.interactionType != OWSInteractionType_IncomingMessage) {
OWSFailDebug(@"not an incoming message.");
2018-06-25 20:31:09 +02:00
return;
}
NSString *recipientId = notification.userInfo[kNSNotificationKey_ProfileRecipientId];
if (recipientId.length == 0) {
return;
}
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)self.viewItem.interaction;
if (![incomingMessage.authorId isEqualToString:recipientId]) {
return;
}
[self updateAvatarView];
}
2017-11-17 16:49:34 +01:00
#pragma mark - Measurement
2018-08-09 16:47:43 +02:00
- (CGSize)cellSize
{
OWSAssertDebug(self.conversationStyle);
OWSAssertDebug(self.conversationStyle.viewWidth > 0);
OWSAssertDebug(self.viewItem);
OWSAssertDebug([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
OWSAssertDebug(self.messageBubbleView);
2018-04-05 19:19:13 +02:00
self.messageBubbleView.viewItem = self.viewItem;
self.messageBubbleView.cellMediaCache = self.delegate.cellMediaCache;
2018-06-22 19:48:23 +02:00
CGSize messageBubbleSize = [self.messageBubbleView measureSize];
2018-04-03 18:35:43 +02:00
2018-04-05 19:19:13 +02:00
CGSize cellSize = messageBubbleSize;
OWSAssertDebug(cellSize.width > 0 && cellSize.height > 0);
2018-07-11 20:12:58 +02:00
if (self.viewItem.hasCellHeader) {
cellSize.height +=
[self.headerView measureWithConversationViewItem:self.viewItem conversationStyle:self.conversationStyle]
.height;
}
2018-07-05 23:27:37 +02:00
if (self.shouldHaveSendFailureBadge) {
2018-07-05 16:25:30 +02:00
cellSize.width += self.sendFailureBadgeSize + self.sendFailureBadgeSpacing;
}
2019-05-13 08:21:07 +02:00
2018-03-28 19:34:33 +02:00
cellSize = CGSizeCeil(cellSize);
return cellSize;
}
2018-06-25 20:31:09 +02:00
#pragma mark - Reuse
2017-11-17 16:49:34 +01:00
2017-10-10 22:13:54 +02:00
- (void)prepareForReuse
{
[super prepareForReuse];
2018-03-28 16:11:01 +02:00
[NSLayoutConstraint deactivateConstraints:self.viewConstraints];
self.viewConstraints = [NSMutableArray new];
2017-10-10 22:13:54 +02:00
2018-04-05 19:19:13 +02:00
[self.messageBubbleView prepareForReuse];
[self.messageBubbleView unloadContent];
2018-07-11 20:12:58 +02:00
[self.headerView removeFromSuperview];
2019-05-13 07:21:16 +02:00
[self.avatarView removeFromSuperview];
2017-11-17 16:49:34 +01:00
self.moderatorIconImageView.image = nil;
[self.moderatorIconImageView removeFromSuperview];
2019-09-12 07:06:20 +02:00
2018-07-05 16:25:30 +02:00
[self.sendFailureBadgeView removeFromSuperview];
self.sendFailureBadgeView = nil;
2018-06-25 20:31:09 +02:00
[[NSNotificationCenter defaultCenter] removeObserver:self];
2017-10-10 22:13:54 +02:00
}
#pragma mark - Notifications
- (void)setIsCellVisible:(BOOL)isCellVisible {
2017-10-17 06:05:29 +02:00
BOOL didChange = self.isCellVisible != isCellVisible;
[super setIsCellVisible:isCellVisible];
if (!didChange) {
return;
}
2017-10-17 06:05:29 +02:00
2017-11-17 16:49:34 +01:00
[self ensureMediaLoadState];
}
2017-10-10 22:13:54 +02:00
#pragma mark - Gesture recognizers
2018-04-17 19:28:06 +02:00
- (void)handleTapGesture:(UITapGestureRecognizer *)sender
{
OWSAssertDebug(self.delegate);
2018-04-17 19:28:06 +02:00
if (sender.state != UIGestureRecognizerStateRecognized) {
OWSLogVerbose(@"Ignoring tap on message: %@", self.viewItem.interaction.debugDescription);
2018-04-17 19:28:06 +02:00
return;
}
if ([self isGestureInCellHeader:sender]) {
return;
}
2018-04-17 19:28:06 +02:00
if (self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction;
2018-04-24 21:49:08 +02:00
if (outgoingMessage.messageState == TSOutgoingMessageStateFailed) {
2018-04-17 19:28:06 +02:00
[self.delegate didTapFailedOutgoingMessage:outgoingMessage];
return;
2018-04-24 21:49:08 +02:00
} else if (outgoingMessage.messageState == TSOutgoingMessageStateSending) {
2018-04-17 19:28:06 +02:00
// Ignore taps on outgoing messages being sent.
return;
}
}
[self.messageBubbleView handleTapGesture:sender];
}
2018-03-27 19:25:02 +02:00
- (void)handleLongPressGesture:(UILongPressGestureRecognizer *)sender
{
OWSAssertDebug(self.delegate);
2018-03-27 19:25:02 +02:00
if (sender.state != UIGestureRecognizerStateBegan) {
return;
}
if ([self isGestureInCellHeader:sender]) {
return;
}
2018-12-18 19:35:49 +01:00
BOOL shouldAllowReply = YES;
2018-04-05 19:19:13 +02:00
if (self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction;
2018-04-23 16:30:51 +02:00
if (outgoingMessage.messageState == TSOutgoingMessageStateFailed) {
// Don't allow "delete" or "reply" on "failed" outgoing messages.
2018-12-18 19:35:49 +01:00
shouldAllowReply = NO;
2018-04-23 16:30:51 +02:00
} else if (outgoingMessage.messageState == TSOutgoingMessageStateSending) {
// Don't allow "delete" or "reply" on "sending" outgoing messages.
2018-12-18 19:35:49 +01:00
shouldAllowReply = NO;
2018-03-27 19:25:02 +02:00
}
}
2018-04-05 19:19:13 +02:00
CGPoint locationInMessageBubble = [sender locationInView:self.messageBubbleView];
switch ([self.messageBubbleView gestureLocationForLocation:locationInMessageBubble]) {
case OWSMessageGestureLocation_Default:
2019-01-18 16:23:07 +01:00
case OWSMessageGestureLocation_OversizeText:
case OWSMessageGestureLocation_LinkPreview: {
[self.delegate conversationCell:self
2018-12-18 19:35:49 +01:00
shouldAllowReply:shouldAllowReply
didLongpressTextViewItem:self.viewItem];
2018-04-05 19:19:13 +02:00
break;
}
case OWSMessageGestureLocation_Media: {
[self.delegate conversationCell:self
2018-12-18 19:35:49 +01:00
shouldAllowReply:shouldAllowReply
didLongpressMediaViewItem:self.viewItem];
2018-04-05 19:19:13 +02:00
break;
}
2018-04-11 17:25:28 +02:00
case OWSMessageGestureLocation_QuotedReply: {
[self.delegate conversationCell:self
2018-12-18 19:35:49 +01:00
shouldAllowReply:shouldAllowReply
didLongpressQuoteViewItem:self.viewItem];
2018-04-05 19:19:13 +02:00
break;
2018-04-11 17:25:28 +02:00
}
2017-10-10 22:13:54 +02:00
}
}
2020-10-19 02:21:04 +02:00
- (void)handlePanGesture:(UIPanGestureRecognizer *)sender
{
[self.messageBubbleView handlePanGesture:sender];
}
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
2021-01-29 01:46:32 +01:00
SNVoiceMessageView *voiceMessageView = self.viewItem.lastAudioMessageView;
if (![gestureRecognizer isKindOfClass:UIPanGestureRecognizer.class] || voiceMessageView == nil) { return NO; }
UIPanGestureRecognizer *panGestureRecognizer = (UIPanGestureRecognizer *)gestureRecognizer;
CGPoint location = [panGestureRecognizer locationInView:voiceMessageView];
if (!CGRectContainsPoint(voiceMessageView.bounds, location)) { return NO; }
CGPoint velocity = [panGestureRecognizer velocityInView:voiceMessageView];
return fabs(velocity.x) > fabs(velocity.y);
}
- (BOOL)isGestureInCellHeader:(UIGestureRecognizer *)sender
{
OWSAssertDebug(self.viewItem);
if (!self.viewItem.hasCellHeader) {
return NO;
}
CGPoint location = [sender locationInView:self];
CGPoint headerBottom = [self convertPoint:CGPointMake(0, self.headerView.height) fromView:self.headerView];
return location.y <= headerBottom.y;
}
2017-10-10 22:13:54 +02:00
@end
NS_ASSUME_NONNULL_END