diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 97ec2831e..7f4ad5a59 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -123,7 +123,7 @@ #import "Riot-Swift.h" -@interface RoomViewController () +@interface RoomViewController () { // The expanded header ExpandedRoomTitleView *expandedHeader; @@ -213,6 +213,10 @@ MXServerNotices *serverNotices; } +@property (nonatomic, weak) IBOutlet UIView *overlayContainerView; + +@property (nonatomic, strong) RoomContextualMenuPresenter *roomContextualMenuPresenter; + @end @implementation RoomViewController @@ -404,6 +408,8 @@ [self refreshRoomInputToolbar]; } + self.roomContextualMenuPresenter = [RoomContextualMenuPresenter new]; + // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -589,6 +595,9 @@ { [super viewDidDisappear:animated]; + // Hide contextual menu if needed + [self hideContextualMenuAnimated:NO]; + // Reset visible room id [AppDelegate theDelegate].visibleRoomId = nil; @@ -936,6 +945,8 @@ - (void)updateRoomInputToolbarViewClassIfNeeded { Class roomInputToolbarViewClass = RoomInputToolbarView.class; + + BOOL shouldDismissContextualMenu = NO; // Check the user has enough power to post message if (self.roomDataSource.roomState) @@ -950,10 +961,12 @@ if (isRoomObsolete || isResourceLimitExceeded) { roomInputToolbarViewClass = nil; + shouldDismissContextualMenu = YES; } else if (!canSend) { roomInputToolbarViewClass = DisabledRoomInputToolbarView.class; + shouldDismissContextualMenu = YES; } } @@ -961,6 +974,12 @@ if (self.isRoomPreview) { roomInputToolbarViewClass = nil; + shouldDismissContextualMenu = YES; + } + + if (shouldDismissContextualMenu) + { + [self hideContextualMenuAnimated:NO]; } // Change inputToolbarView class only if given class is different from current one @@ -978,7 +997,7 @@ if ([self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) { - height = ((RoomInputToolbarView*)self.inputToolbarView).mainToolbarMinHeightConstraint.constant; + height = ((RoomInputToolbarView*)self.inputToolbarView).mainToolbarHeightConstraint.constant; } else if ([self.inputToolbarView isKindOfClass:DisabledRoomInputToolbarView.class]) { @@ -1485,6 +1504,14 @@ [UIView setAnimationsEnabled:YES]; } +- (void)handleLongPressFromCell:(id)cell withTappedEvent:(MXEvent*)event +{ + if (event && !customizedRoomDataSource.selectedEventId) + { + [self showContextualMenuForEvent:event cell:cell animated:YES]; + } +} + #pragma mark - Hide/Show expanded header - (void)showExpandedHeader:(BOOL)isVisible @@ -1552,6 +1579,9 @@ mainNavigationController.navigationBar.translucent = isVisible; self.navigationController.navigationBar.translucent = isVisible; + // Hide contextual menu if needed + [self hideContextualMenuAnimated:YES]; + [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn animations:^{ @@ -2030,9 +2060,6 @@ [self selectEventWithId:tappedEvent.eventId]; } } - - // Force table refresh - [self dataSource:self.roomDataSource didCellChange:nil]; } else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnOverlayContainer]) { @@ -2073,9 +2100,6 @@ // Highlight this event in displayed message [self selectEventWithId:((MXKRoomBubbleTableViewCell*)cell).bubbleData.attachment.eventId]; } - - // Force table refresh - [self dataSource:self.roomDataSource didCellChange:nil]; } else { @@ -2105,6 +2129,11 @@ [self.roomDataSource collapseRoomBubble:((MXKRoomBubbleTableViewCell*)cell).bubbleData collapsed:YES]; } + else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellLongPressOnEvent]) + { + MXEvent *tappedEvent = userInfo[kMXKRoomBubbleCellEventKey]; + [self handleLongPressFromCell:cell withTappedEvent:tappedEvent]; + } else { // Keep default implementation for other actions @@ -2213,24 +2242,6 @@ }]]; } } - - if (level == 0) - { - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_copy", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self cancelEventSelection]; - - [[UIPasteboard generalPasteboard] setString:selectedComponent.textMessage]; - } - - }]]; - } if (level == 0) { @@ -2323,42 +2334,6 @@ }]]; } - if (attachment.type != MXKAttachmentTypeSticker) - { - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_copy", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self cancelEventSelection]; - - [self startActivityIndicator]; - - [attachment copy:^{ - - __strong __typeof(weakSelf)self = weakSelf; - [self stopActivityIndicator]; - - } failure:^(NSError *error) { - - __strong __typeof(weakSelf)self = weakSelf; - [self stopActivityIndicator]; - - //Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; - - // Start animation in case of download during attachment preparing - [roomBubbleTableViewCell startProgressUI]; - } - - }]]; - } - // Check status of the selected event if (selectedEvent.sentState == MXEventSentStatePreparing || selectedEvent.sentState == MXEventSentStateEncrypting || @@ -2733,7 +2708,7 @@ if (weakSelf) { typeof(self) self = weakSelf; - [self cancelEventSelection]; + [self hideContextualMenuAnimated:YES]; } }]]; @@ -2843,10 +2818,8 @@ else if (url && urlItemInteractionValue) { // Fallback case for external links - - // TODO: Use UITextItemInteraction enum when minimum deployement target will be iOS 10 switch (urlItemInteractionValue.integerValue) { - case 0: //UITextItemInteractionInvokeDefaultAction + case UITextItemInteractionInvokeDefaultAction: { [[UIApplication sharedApplication] vc_open:url completionHandler:^(BOOL success) { if (!success) @@ -2857,10 +2830,13 @@ shouldDoAction = NO; } break; - case 1: //UITextItemInteractionPresentActions - // Long press on link, let MXKRoomBubbleTableViewCell UITextView present the default contextual menu. + case UITextItemInteractionPresentActions: + { + // Long press on link, present room contextual menu. + shouldDoAction = NO; + } break; - case 2: //UITextItemInteractionPreview + case UITextItemInteractionPreview: // Force touch on link, let MXKRoomBubbleTableViewCell UITextView use default peek and pop behavior. break; default: @@ -2879,10 +2855,19 @@ - (void)selectEventWithId:(NSString*)eventId { BOOL shouldEnableReplyMode = [self.roomDataSource canReplyToEventWithId:eventId]; + + [self selectEventWithId:eventId enableReplyMode:shouldEnableReplyMode showTimestamp:YES]; +} - [self setInputToolBarSendMode: shouldEnableReplyMode ? RoomInputToolbarViewSendModeReply : RoomInputToolbarViewSendModeSend]; - +- (void)selectEventWithId:(NSString*)eventId enableReplyMode:(BOOL)enableReplyMode showTimestamp:(BOOL)showTimestamp +{ + [self setInputToolBarSendMode: enableReplyMode ? RoomInputToolbarViewSendModeReply : RoomInputToolbarViewSendModeSend]; + + customizedRoomDataSource.showBubbleDateTimeOnSelection = showTimestamp; customizedRoomDataSource.selectedEventId = eventId; + + // Force table refresh + [self dataSource:self.roomDataSource didCellChange:nil]; } - (void)cancelEventSelection @@ -2895,6 +2880,7 @@ currentAlert = nil; } + customizedRoomDataSource.showBubbleDateTimeOnSelection = YES; customizedRoomDataSource.selectedEventId = nil; // Force table refresh @@ -4976,5 +4962,169 @@ } } +#pragma mark - Contextual Menu + +- (NSArray*)contextualMenuItemsForEvent:(MXEvent*)event andCell:(id)cell +{ + NSString *eventId = event.eventId; + MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell *)cell; + MXKAttachment *attachment = roomBubbleTableViewCell.bubbleData.attachment; + + MXWeakify(self); + + // Copy action + + RoomContextualMenuItem *copyMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionCopy]; + copyMenuItem.isEnabled = !attachment || attachment.type != MXKAttachmentTypeSticker; + copyMenuItem.action = ^{ + MXStrongifyAndReturnIfNil(self); + + if (!attachment) + { + NSArray *components = roomBubbleTableViewCell.bubbleData.bubbleComponents; + MXKRoomBubbleComponent *selectedComponent; + for (selectedComponent in components) + { + if ([selectedComponent.event.eventId isEqualToString:event.eventId]) + { + break; + } + selectedComponent = nil; + } + NSString *textMessage = selectedComponent.textMessage; + + [UIPasteboard generalPasteboard].string = textMessage; + + [self hideContextualMenuAnimated:YES]; + } + else if (attachment.type != MXKAttachmentTypeSticker) + { + [self hideContextualMenuAnimated:YES completion:^{ + [self startActivityIndicator]; + + [attachment copy:^{ + + [self stopActivityIndicator]; + + } failure:^(NSError *error) { + + [self stopActivityIndicator]; + + //Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; + + // Start animation in case of download during attachment preparing + [roomBubbleTableViewCell startProgressUI]; + }]; + } + }; + + // Reply action + + RoomContextualMenuItem *replyMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionReply]; + replyMenuItem.isEnabled = [self.roomDataSource canReplyToEventWithId:eventId]; + replyMenuItem.action = ^{ + MXStrongifyAndReturnIfNil(self); + + [self hideContextualMenuAnimated:YES cancelEventSelection:NO completion:nil]; + [self selectEventWithId:eventId enableReplyMode:YES showTimestamp:NO]; + }; + + // Edit action + + RoomContextualMenuItem *editMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionEdit]; + // TODO: Handle edit action + editMenuItem.isEnabled = NO; + + // More action + + RoomContextualMenuItem *moreMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionMore]; + moreMenuItem.action = ^{ + MXStrongifyAndReturnIfNil(self); + [self showEditButtonAlertMenuForEvent:event inCell:cell level:0]; + }; + + // Actions list + + NSArray *actionItems = @[ + copyMenuItem, + replyMenuItem, + editMenuItem, + moreMenuItem + ]; + + return actionItems; +} + +- (void)showContextualMenuForEvent:(MXEvent*)event cell:(id)cell animated:(BOOL)animated +{ + if (self.roomContextualMenuPresenter.isPresenting) + { + return; + } + + [self selectEventWithId:event.eventId enableReplyMode:NO showTimestamp:NO]; + + NSArray* contextualMenuItems = [self contextualMenuItemsForEvent:event andCell:cell]; + + RoomContextualMenuViewController *roomContextualMenuViewController = [RoomContextualMenuViewController instantiateWith:contextualMenuItems]; + roomContextualMenuViewController.delegate = self; + + [self.roomContextualMenuPresenter presentWithRoomContextualMenuViewController:roomContextualMenuViewController + from:self + on:self.overlayContainerView + animated:YES + completion:^{ + [self contextualMenuAnimationCompletionAfterBeingShown:YES]; + }]; +} + +- (void)hideContextualMenuAnimated:(BOOL)animated +{ + [self hideContextualMenuAnimated:animated completion:nil]; +} + +- (void)hideContextualMenuAnimated:(BOOL)animated completion:(void(^)(void))completion +{ + [self hideContextualMenuAnimated:animated cancelEventSelection:YES completion:completion]; +} + +- (void)hideContextualMenuAnimated:(BOOL)animated cancelEventSelection:(BOOL)cancelEventSelection completion:(void(^)(void))completion +{ + if (!self.roomContextualMenuPresenter.isPresenting) + { + return; + } + + if (cancelEventSelection) + { + [self cancelEventSelection]; + } + + [self.roomContextualMenuPresenter hideContextualMenuWithAnimated:animated completion:^{ + [self contextualMenuAnimationCompletionAfterBeingShown:NO]; + + if (completion) + { + completion(); + } + }]; +} + +- (void)contextualMenuAnimationCompletionAfterBeingShown:(BOOL)isShown +{ + self.inputToolbarView.editable = !isShown; + self.bubblesTableView.scrollsToTop = !isShown; + self.overlayContainerView.userInteractionEnabled = isShown; +} + +#pragma mark - RoomContextualMenuViewControllerDelegate + +- (void)roomContextualMenuViewControllerDidTapBackgroundOverlay:(RoomContextualMenuViewController *)viewController +{ + [self hideContextualMenuAnimated:YES]; +} + @end diff --git a/Riot/Modules/Room/RoomViewController.xib b/Riot/Modules/Room/RoomViewController.xib index 0a8a14425..6ea9ec67f 100644 --- a/Riot/Modules/Room/RoomViewController.xib +++ b/Riot/Modules/Room/RoomViewController.xib @@ -1,11 +1,11 @@ - + - + @@ -21,6 +21,7 @@ + @@ -130,7 +131,7 @@ - + @@ -146,10 +147,15 @@ + + + + + @@ -160,12 +166,15 @@ + + + @@ -174,7 +183,7 @@ - - + +