session-ios/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m

667 lines
22 KiB
Mathematica
Raw Normal View History

2017-10-10 22:13:54 +02:00
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
2017-10-10 22:13:54 +02:00
//
#import "OWSMessageCell.h"
#import "OWSExpirationTimerView.h"
2018-04-05 19:26:15 +02:00
#import "OWSMessageBubbleView.h"
2017-10-10 22:13:54 +02:00
#import "Signal-Swift.h"
2018-04-05 19:19:13 +02:00
2017-10-10 22:13:54 +02:00
NS_ASSUME_NONNULL_BEGIN
@interface OWSMessageCell ()
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-03-28 16:11:01 +02:00
// The cell's contentView contains:
2017-11-17 16:49:34 +01:00
//
// * MessageView (message)
// * dateHeaderLabel (above message)
// * footerView (below message)
2018-03-28 16:11:01 +02:00
// * failedSendBadgeView ("trailing" beside message)
2017-11-17 16:49:34 +01:00
2018-04-05 19:19:13 +02:00
@property (nonatomic) OWSMessageBubbleView *messageBubbleView;
@property (nonatomic) UILabel *dateHeaderLabel;
@property (nonatomic, nullable) UIImageView *failedSendBadgeView;
@property (nonatomic) UIView *footerView;
@property (nonatomic) UILabel *footerLabel;
@property (nonatomic, nullable) OWSExpirationTimerView *expirationTimerView;
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
@implementation OWSMessageCell
// `[UIView init]` invokes `[self initWithFrame:...]`.
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self commontInit];
}
return self;
}
- (void)commontInit
{
2018-04-05 19:19:13 +02:00
OWSAssert(!self.messageBubbleView);
2017-10-10 22:13:54 +02:00
2018-03-28 16:11:01 +02:00
_viewConstraints = [NSMutableArray new];
2017-12-18 23:48:07 +01:00
2017-10-10 22:13:54 +02:00
self.layoutMargins = UIEdgeInsetsZero;
self.contentView.layoutMargins = UIEdgeInsetsZero;
2017-10-10 22:13:54 +02:00
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
self.footerView = [UIView containerView];
[self.contentView addSubview:self.footerView];
self.dateHeaderLabel = [UILabel new];
self.dateHeaderLabel.font = self.dateHeaderDateFont;
self.dateHeaderLabel.textAlignment = NSTextAlignmentCenter;
self.dateHeaderLabel.textColor = [UIColor lightGrayColor];
[self.contentView addSubview:self.dateHeaderLabel];
self.footerLabel = [UILabel new];
self.footerLabel.font = UIFont.ows_dynamicTypeCaption1Font;
self.footerLabel.textColor = [UIColor lightGrayColor];
[self.footerView addSubview:self.footerLabel];
2017-10-10 22:13:54 +02:00
// Hide these views by default.
self.dateHeaderLabel.hidden = YES;
self.footerLabel.hidden = YES;
2018-04-05 19:19:13 +02:00
[self.messageBubbleView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.dateHeaderLabel];
[self.footerView autoPinEdgeToSuperviewEdge:ALEdgeBottom];
[self.footerView autoPinWidthToSuperview];
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
UILongPressGestureRecognizer *longPress =
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)];
2018-03-28 20:46:16 +02:00
[self.contentView addGestureRecognizer:longPress];
2018-03-27 19:25:02 +02:00
PanDirectionGestureRecognizer *panGesture = [[PanDirectionGestureRecognizer alloc]
initWithDirection:(self.isRTL ? PanDirectionLeft : PanDirectionRight)target:self
action:@selector(handlePanGesture:)];
[self addGestureRecognizer:panGesture];
2017-11-17 16:49:34 +01:00
}
2017-10-17 06:05:29 +02:00
+ (NSString *)cellReuseIdentifier
{
return NSStringFromClass([self class]);
}
2017-10-27 06:19:58 +02:00
- (BOOL)shouldHaveFailedSendBadge
{
if (![self.viewItem.interaction isKindOfClass:[TSOutgoingMessage class]]) {
return NO;
}
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction;
return outgoingMessage.messageState == TSOutgoingMessageStateUnsent;
}
- (UIImage *)failedSendBadge
{
UIImage *image = [UIImage imageNamed:@"message_send_failure"];
OWSAssert(image);
OWSAssert(image.size.width == self.failedSendBadgeSize && image.size.height == self.failedSendBadgeSize);
return image;
}
- (CGFloat)failedSendBadgeSize
{
return 20.f;
}
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
{
OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
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;
}
2017-11-17 16:49:34 +01:00
#pragma mark - Load
- (void)loadForDisplay
2017-10-10 22:13:54 +02:00
{
OWSAssert(self.viewItem);
OWSAssert(self.viewItem.interaction);
OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
2018-03-28 19:34:33 +02:00
OWSAssert(self.contentWidth > 0);
2018-04-05 19:19:13 +02:00
OWSAssert(self.messageBubbleView);
2018-03-28 19:34:33 +02:00
2018-04-05 19:19:13 +02:00
self.messageBubbleView.viewItem = self.viewItem;
self.messageBubbleView.contentWidth = self.contentWidth;
self.messageBubbleView.cellMediaCache = self.delegate.cellMediaCache;
[self.messageBubbleView configureViews];
[self.messageBubbleView loadContent];
2018-03-28 16:01:01 +02:00
2017-10-27 06:19:58 +02:00
if (self.shouldHaveFailedSendBadge) {
self.failedSendBadgeView = [UIImageView new];
self.failedSendBadgeView.image =
[self.failedSendBadge imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
self.failedSendBadgeView.tintColor = [UIColor ows_destructiveRedColor];
[self.contentView addSubview:self.failedSendBadgeView];
2018-03-28 16:11:01 +02:00
[self.viewConstraints addObjectsFromArray:@[
2018-04-05 19:19:13 +02:00
[self.messageBubbleView autoPinLeadingToSuperviewMargin],
[self.failedSendBadgeView autoPinLeadingToTrailingEdgeOfView:self.messageBubbleView],
[self.failedSendBadgeView autoAlignAxis:ALAxisHorizontal toSameAxisOfView:self.messageBubbleView],
[self.failedSendBadgeView autoPinTrailingToSuperviewMargin],
[self.failedSendBadgeView autoSetDimension:ALDimensionWidth toSize:self.failedSendBadgeSize],
[self.failedSendBadgeView autoSetDimension:ALDimensionHeight toSize:self.failedSendBadgeSize],
2018-03-28 16:11:01 +02:00
]];
} else {
2018-03-28 16:11:01 +02:00
[self.viewConstraints addObjectsFromArray:@[
2018-04-05 19:19:13 +02:00
[self.messageBubbleView autoPinLeadingToSuperviewMargin],
[self.messageBubbleView autoPinTrailingToSuperviewMargin],
2018-03-28 16:11:01 +02:00
]];
}
[self updateDateHeader];
[self updateFooter];
}
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
{
2018-04-05 19:19:13 +02:00
OWSAssert(self.messageBubbleView);
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];
}
}
- (void)updateDateHeader
{
OWSAssert(self.contentWidth > 0);
static NSDateFormatter *dateHeaderDateFormatter = nil;
static NSDateFormatter *dateHeaderTimeFormatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dateHeaderDateFormatter = [NSDateFormatter new];
[dateHeaderDateFormatter setLocale:[NSLocale currentLocale]];
[dateHeaderDateFormatter setDoesRelativeDateFormatting:YES];
[dateHeaderDateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateHeaderDateFormatter setTimeStyle:NSDateFormatterNoStyle];
dateHeaderTimeFormatter = [NSDateFormatter new];
[dateHeaderTimeFormatter setLocale:[NSLocale currentLocale]];
[dateHeaderTimeFormatter setDoesRelativeDateFormatting:YES];
[dateHeaderTimeFormatter setDateStyle:NSDateFormatterNoStyle];
[dateHeaderTimeFormatter setTimeStyle:NSDateFormatterShortStyle];
});
if (self.viewItem.shouldShowDate) {
NSDate *date = self.viewItem.interaction.dateForSorting;
NSString *dateString = [dateHeaderDateFormatter stringFromDate:date];
NSString *timeString = [dateHeaderTimeFormatter stringFromDate:date];
NSAttributedString *attributedText = [NSAttributedString new];
attributedText = [attributedText rtlSafeAppend:dateString
attributes:@{
NSFontAttributeName : self.dateHeaderDateFont,
NSForegroundColorAttributeName : [UIColor lightGrayColor],
}
referenceView:self];
attributedText = [attributedText rtlSafeAppend:@" "
attributes:@{
NSFontAttributeName : self.dateHeaderDateFont,
}
referenceView:self];
attributedText = [attributedText rtlSafeAppend:timeString
attributes:@{
NSFontAttributeName : self.dateHeaderTimeFont,
NSForegroundColorAttributeName : [UIColor lightGrayColor],
}
referenceView:self];
self.dateHeaderLabel.attributedText = attributedText;
self.dateHeaderLabel.hidden = NO;
2018-03-28 16:11:01 +02:00
[self.viewConstraints addObjectsFromArray:@[
// Date headers should be visually centered within the conversation view,
// so they need to extend outside the cell's boundaries.
[self.dateHeaderLabel autoSetDimension:ALDimensionWidth toSize:self.contentWidth],
(self.isIncoming ? [self.dateHeaderLabel autoPinEdgeToSuperviewEdge:ALEdgeLeading]
: [self.dateHeaderLabel autoPinEdgeToSuperviewEdge:ALEdgeTrailing]),
2017-10-17 06:05:29 +02:00
[self.dateHeaderLabel autoPinEdgeToSuperviewEdge:ALEdgeTop],
[self.dateHeaderLabel autoSetDimension:ALDimensionHeight toSize:self.dateHeaderHeight],
2018-03-28 16:11:01 +02:00
]];
} else {
self.dateHeaderLabel.hidden = YES;
2018-03-28 16:11:01 +02:00
[self.viewConstraints addObjectsFromArray:@[
[self.dateHeaderLabel autoSetDimension:ALDimensionHeight toSize:0],
[self.dateHeaderLabel autoPinEdgeToSuperviewEdge:ALEdgeTop],
2018-03-28 16:11:01 +02:00
]];
}
}
2018-04-10 16:53:29 +02:00
- (BOOL)shouldShowFooter
{
2018-04-10 16:53:29 +02:00
BOOL shouldShowFooter = NO;
2018-04-10 16:31:59 +02:00
if (self.message.shouldStartExpireTimer) {
2018-04-10 16:53:29 +02:00
shouldShowFooter = YES;
2017-10-17 06:05:29 +02:00
} else if (self.isOutgoing) {
2018-04-10 16:53:29 +02:00
shouldShowFooter = !self.viewItem.shouldHideRecipientStatus;
} else if (self.viewItem.isGroupThread) {
2018-04-10 16:53:29 +02:00
shouldShowFooter = YES;
} else {
2018-04-10 16:53:29 +02:00
shouldShowFooter = NO;
}
2018-04-10 16:53:29 +02:00
return shouldShowFooter;
2018-04-10 16:31:59 +02:00
}
- (CGFloat)footerHeight
{
2018-04-10 16:53:29 +02:00
if (!self.shouldShowFooter) {
2018-04-10 16:31:59 +02:00
return 0.f;
}
return ceil(MAX(kExpirationTimerViewSize, self.footerLabel.font.lineHeight));
}
- (CGFloat)footerVSpacing
{
return 1.f;
}
- (void)updateFooter
{
OWSAssert(self.viewItem.interaction.interactionType == OWSInteractionType_IncomingMessage
|| self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage);
2017-10-17 06:05:29 +02:00
TSMessage *message = self.message;
BOOL hasExpirationTimer = message.shouldStartExpireTimer;
NSAttributedString *attributedText = nil;
2017-10-17 06:05:29 +02:00
if (self.isOutgoing) {
if (!self.viewItem.shouldHideRecipientStatus || hasExpirationTimer) {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)message;
NSString *statusMessage =
[MessageRecipientStatusUtils statusMessageWithOutgoingMessage:outgoingMessage referenceView:self];
attributedText = [[NSAttributedString alloc] initWithString:statusMessage attributes:@{}];
}
} else if (self.viewItem.isGroupThread) {
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)self.viewItem.interaction;
attributedText = [self.delegate attributedContactOrProfileNameForPhoneIdentifier:incomingMessage.authorId];
}
if (!hasExpirationTimer &&
!attributedText) {
self.footerLabel.hidden = YES;
2018-03-28 16:11:01 +02:00
[self.viewConstraints addObjectsFromArray:@[
2018-04-10 16:31:59 +02:00
[self.footerView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.messageBubbleView],
2018-03-28 16:11:01 +02:00
[self.footerView autoSetDimension:ALDimensionHeight toSize:0],
]];
return;
}
2017-10-17 06:05:29 +02:00
2018-04-10 16:53:29 +02:00
[self.viewConstraints addObject:[self.footerView autoPinEdge:ALEdgeTop
toEdge:ALEdgeBottom
ofView:self.messageBubbleView
withOffset:self.footerVSpacing]];
2018-04-10 16:31:59 +02:00
2017-10-17 06:05:29 +02:00
if (hasExpirationTimer) {
uint64_t expirationTimestamp = message.expiresAt;
uint32_t expiresInSeconds = message.expiresInSeconds;
self.expirationTimerView = [[OWSExpirationTimerView alloc] initWithExpiration:expirationTimestamp
initialDurationSeconds:expiresInSeconds];
[self.footerView addSubview:self.expirationTimerView];
}
if (attributedText) {
self.footerLabel.attributedText = attributedText;
self.footerLabel.hidden = NO;
}
if (hasExpirationTimer &&
attributedText) {
2018-03-28 16:11:01 +02:00
[self.viewConstraints addObjectsFromArray:@[
[self.expirationTimerView autoVCenterInSuperview],
[self.footerLabel autoVCenterInSuperview],
(self.isIncoming ? [self.expirationTimerView autoPinLeadingToSuperviewMargin]
: [self.expirationTimerView autoPinTrailingToSuperviewMargin]),
(self.isIncoming ? [self.footerLabel autoPinLeadingToTrailingEdgeOfView:self.expirationTimerView offset:0.f]
: [self.footerLabel autoPinTrailingToLeadingEdgeOfView:self.expirationTimerView offset:0.f]),
2018-03-28 16:11:01 +02:00
[self.footerView autoSetDimension:ALDimensionHeight toSize:self.footerHeight],
]];
} else if (hasExpirationTimer) {
2018-03-28 16:11:01 +02:00
[self.viewConstraints addObjectsFromArray:@[
[self.expirationTimerView autoVCenterInSuperview],
(self.isIncoming ? [self.expirationTimerView autoPinLeadingToSuperviewMargin]
: [self.expirationTimerView autoPinTrailingToSuperviewMargin]),
2018-03-28 16:11:01 +02:00
[self.footerView autoSetDimension:ALDimensionHeight toSize:self.footerHeight],
]];
} else if (attributedText) {
2018-03-28 16:11:01 +02:00
[self.viewConstraints addObjectsFromArray:@[
[self.footerLabel autoVCenterInSuperview],
(self.isIncoming ? [self.footerLabel autoPinLeadingToSuperviewMargin]
: [self.footerLabel autoPinTrailingToSuperviewMargin]),
2018-03-28 16:11:01 +02:00
[self.footerView autoSetDimension:ALDimensionHeight toSize:self.footerHeight],
]];
} else {
OWSFail(@"%@ Cell unexpectedly has neither expiration timer nor footer text.", self.logTag);
}
}
- (UIFont *)dateHeaderDateFont
{
return UIFont.ows_dynamicTypeCaption1Font.ows_medium;
}
- (UIFont *)dateHeaderTimeFont
{
return UIFont.ows_dynamicTypeCaption1Font;
}
2017-11-17 16:49:34 +01:00
#pragma mark - Measurement
- (CGSize)cellSizeForViewWidth:(int)viewWidth contentWidth:(int)contentWidth
{
OWSAssert(self.viewItem);
OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
2018-04-05 19:19:13 +02:00
OWSAssert(self.messageBubbleView);
2018-04-05 19:19:13 +02:00
self.messageBubbleView.viewItem = self.viewItem;
self.messageBubbleView.contentWidth = self.contentWidth;
self.messageBubbleView.cellMediaCache = self.delegate.cellMediaCache;
CGSize messageBubbleSize = [self.messageBubbleView sizeForContentWidth:contentWidth];
2018-04-03 18:35:43 +02:00
2018-04-05 19:19:13 +02:00
CGSize cellSize = messageBubbleSize;
OWSAssert(cellSize.width > 0 && cellSize.height > 0);
cellSize.height += self.dateHeaderHeight;
2018-04-10 16:53:29 +02:00
if (self.shouldShowFooter) {
2018-04-10 16:31:59 +02:00
cellSize.height += self.footerVSpacing;
cellSize.height += self.footerHeight;
}
2017-10-27 06:19:58 +02:00
if (self.shouldHaveFailedSendBadge) {
cellSize.width += self.failedSendBadgeSize;
}
2018-03-28 19:34:33 +02:00
cellSize = CGSizeCeil(cellSize);
return cellSize;
}
- (CGFloat)dateHeaderHeight
{
if (self.viewItem.shouldShowDate) {
// Add 5pt spacing above and below the date header.
2018-03-28 19:34:33 +02:00
return (CGFloat)ceil(MAX(self.dateHeaderDateFont.lineHeight, self.dateHeaderTimeFont.lineHeight) + 10.f);
} else {
return 0.f;
}
2017-10-10 22:13:54 +02:00
}
2017-11-17 16:49:34 +01:00
#pragma mark -
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];
self.dateHeaderLabel.text = nil;
self.dateHeaderLabel.hidden = YES;
[self.failedSendBadgeView removeFromSuperview];
self.failedSendBadgeView = nil;
self.footerLabel.text = nil;
self.footerLabel.hidden = YES;
2017-11-17 16:49:34 +01:00
[self.expirationTimerView clearAnimations];
[self.expirationTimerView removeFromSuperview];
self.expirationTimerView = nil;
[self hideMenuControllerIfNecessary];
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];
if (isCellVisible) {
2017-10-17 06:05:29 +02:00
if (self.message.shouldStartExpireTimer) {
[self.expirationTimerView ensureAnimations];
} else {
[self.expirationTimerView clearAnimations];
}
} else {
[self.expirationTimerView clearAnimations];
[self hideMenuControllerIfNecessary];
}
}
2017-10-10 22:13:54 +02:00
#pragma mark - Gesture recognizers
2018-03-27 19:25:02 +02:00
- (void)handleLongPressGesture:(UILongPressGestureRecognizer *)sender
{
OWSAssert(self.delegate);
if (sender.state != UIGestureRecognizerStateBegan) {
return;
}
2018-04-05 19:19:13 +02:00
if (self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction;
if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) {
// Ignore long press on unsent messages.
return;
} else if (outgoingMessage.messageState == TSOutgoingMessageStateAttemptingOut) {
// Ignore long press on outgoing messages being sent.
2018-03-27 19:25:02 +02:00
return;
}
}
2018-04-05 19:19:13 +02:00
CGPoint locationInMessageBubble = [sender locationInView:self.messageBubbleView];
switch ([self.messageBubbleView gestureLocationForLocation:locationInMessageBubble]) {
case OWSMessageGestureLocation_Default:
case OWSMessageGestureLocation_OversizeText: {
CGPoint location = [sender locationInView:self];
[self showTextMenuController:location];
break;
}
case OWSMessageGestureLocation_Media: {
CGPoint location = [sender locationInView:self];
[self showMediaMenuController:location];
break;
}
2018-04-11 17:25:28 +02:00
case OWSMessageGestureLocation_QuotedReply: {
CGPoint location = [sender locationInView:self];
[self showDefaultMenuController:location];
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
}
}
- (void)handlePanGesture:(UIPanGestureRecognizer *)panRecognizer
{
OWSAssert(self.delegate);
[self.delegate didPanWithGestureRecognizer:panRecognizer viewItem:self.viewItem];
}
2017-10-10 22:13:54 +02:00
#pragma mark - UIMenuController
- (void)showTextMenuController:(CGPoint)fromLocation
{
2018-04-11 17:25:28 +02:00
[self showMenuController:fromLocation menuItems:self.viewItem.textMenuControllerItems];
}
2018-04-11 17:25:28 +02:00
- (void)showMediaMenuController:(CGPoint)fromLocation
{
[self showMenuController:fromLocation menuItems:self.viewItem.mediaMenuControllerItems];
}
2018-04-11 17:25:28 +02:00
- (void)showDefaultMenuController:(CGPoint)fromLocation
{
[self showMenuController:fromLocation menuItems:self.viewItem.defaultMenuControllerItems];
}
2018-04-11 17:25:28 +02:00
- (void)showMenuController:(CGPoint)fromLocation menuItems:(NSArray *)menuItems
2017-10-10 22:13:54 +02:00
{
2018-04-11 17:25:28 +02:00
if (menuItems.count < 1) {
OWSFail(@"%@ No menu items to present.", self.logTag);
return;
}
// We don't want taps on messages to hide the keyboard,
// so we only let messages become first responder
// while they are trying to present the menu controller.
self.isPresentingMenuController = YES;
2017-10-10 22:13:54 +02:00
[self becomeFirstResponder];
if ([UIMenuController sharedMenuController].isMenuVisible) {
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:NO];
}
// We use custom action selectors so that we can control
// the ordering of the actions in the menu.
[UIMenuController sharedMenuController].menuItems = menuItems;
CGRect targetRect = CGRectMake(fromLocation.x, fromLocation.y, 1, 1);
[[UIMenuController sharedMenuController] setTargetRect:targetRect inView:self];
[[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES];
}
- (BOOL)canPerformAction:(SEL)action withSender:(nullable id)sender
{
return [self.viewItem canPerformAction:action];
}
- (void)copyTextAction:(nullable id)sender
{
[self.viewItem copyTextAction];
}
- (void)copyMediaAction:(nullable id)sender
{
[self.viewItem copyMediaAction];
}
- (void)shareTextAction:(nullable id)sender
2017-10-10 22:13:54 +02:00
{
[self.viewItem shareTextAction];
2017-10-10 22:13:54 +02:00
}
- (void)shareMediaAction:(nullable id)sender
2017-10-10 22:13:54 +02:00
{
[self.viewItem shareMediaAction];
2017-10-10 22:13:54 +02:00
}
- (void)saveMediaAction:(nullable id)sender
2017-10-10 22:13:54 +02:00
{
[self.viewItem saveMediaAction];
2017-10-10 22:13:54 +02:00
}
- (void)deleteAction:(nullable id)sender
{
[self.viewItem deleteAction];
}
- (void)metadataAction:(nullable id)sender
{
OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
[self.delegate showMetadataViewForViewItem:self.viewItem];
2017-10-10 22:13:54 +02:00
}
- (void)replyAction:(nullable id)sender
{
OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
[self.delegate conversationCell:self didTapReplyForViewItem:self.viewItem];
}
2017-10-10 22:13:54 +02:00
- (BOOL)canBecomeFirstResponder
{
return self.isPresentingMenuController;
}
- (void)didHideMenuController:(NSNotification *)notification
{
self.isPresentingMenuController = NO;
}
- (void)setIsPresentingMenuController:(BOOL)isPresentingMenuController
{
if (_isPresentingMenuController == isPresentingMenuController) {
return;
}
_isPresentingMenuController = isPresentingMenuController;
if (isPresentingMenuController) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didHideMenuController:)
name:UIMenuControllerDidHideMenuNotification
object:nil];
} else {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIMenuControllerDidHideMenuNotification
object:nil];
}
}
- (void)hideMenuControllerIfNecessary
{
if (self.isPresentingMenuController) {
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:NO];
}
self.isPresentingMenuController = NO;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
2017-10-10 22:13:54 +02:00
}
@end
NS_ASSUME_NONNULL_END