RoomViewController: Show contextual menu toolbar on long press.

This commit is contained in:
SBiOSoftWhare
2019-05-15 23:24:34 +02:00
parent a603de3573
commit 64c43b48c2
2 changed files with 235 additions and 76 deletions
+221 -71
View File
@@ -123,7 +123,7 @@
#import "Riot-Swift.h"
@interface RoomViewController () <UISearchBarDelegate, UIGestureRecognizerDelegate, RoomTitleViewTapGestureDelegate, RoomParticipantsViewControllerDelegate, MXKRoomMemberDetailsViewControllerDelegate, ContactsTableViewControllerDelegate, MXServerNoticesDelegate>
@interface RoomViewController () <UISearchBarDelegate, UIGestureRecognizerDelegate, RoomTitleViewTapGestureDelegate, RoomParticipantsViewControllerDelegate, MXKRoomMemberDetailsViewControllerDelegate, ContactsTableViewControllerDelegate, MXServerNoticesDelegate, RoomContextualMenuViewControllerDelegate>
{
// 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<MXKCellRendering>)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<RoomContextualMenuItem*>*)contextualMenuItemsForEvent:(MXEvent*)event andCell:(id<MXKCellRendering>)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<RoomContextualMenuItem*> *actionItems = @[
copyMenuItem,
replyMenuItem,
editMenuItem,
moreMenuItem
];
return actionItems;
}
- (void)showContextualMenuForEvent:(MXEvent*)event cell:(id<MXKCellRendering>)cell animated:(BOOL)animated
{
if (self.roomContextualMenuPresenter.isPresenting)
{
return;
}
[self selectEventWithId:event.eventId enableReplyMode:NO showTimestamp:NO];
NSArray<RoomContextualMenuItem*>* 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
+14 -5
View File
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -21,6 +21,7 @@
<outlet property="jumpToLastUnreadBannerSeparatorView" destination="knN-q1-QkJ" id="hHJ-c8-QfN"/>
<outlet property="jumpToLastUnreadButton" destination="ISb-UT-u0O" id="fs0-sQ-lRe"/>
<outlet property="jumpToLastUnreadLabel" destination="S1q-B4-Df3" id="McV-gv-bUa"/>
<outlet property="overlayContainerView" destination="gt1-EO-UVY" id="5q6-pW-UyZ"/>
<outlet property="previewHeaderContainer" destination="54r-18-K1g" id="Klt-RV-V1E"/>
<outlet property="previewHeaderContainerHeightConstraint" destination="goj-GZ-IkD" id="GbA-T9-kiL"/>
<outlet property="resetReadMarkerButton" destination="c4g-BY-xOo" id="KuR-hH-rz1"/>
@@ -130,7 +131,7 @@
<constraint firstItem="ISb-UT-u0O" firstAttribute="centerY" secondItem="Vlz-UJ-Jz8" secondAttribute="centerY" id="w7t-WC-VjP"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XX4-n6-hCm" userLabel="Activities Container">
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XX4-n6-hCm" userLabel="Activities Container">
<rect key="frame" x="0.0" y="606" width="375" height="20"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="RoomVCActivitiesContainer"/>
@@ -146,10 +147,15 @@
<constraint firstAttribute="height" constant="41" id="5eD-Fm-RDb"/>
</constraints>
</view>
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="gt1-EO-UVY">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="RoomVCView"/>
<constraints>
<constraint firstItem="gt1-EO-UVY" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="0Gc-VV-1ao"/>
<constraint firstAttribute="trailing" secondItem="BGD-sd-SQR" secondAttribute="trailing" id="0la-ok-MBr"/>
<constraint firstItem="S6r-bo-jxw" firstAttribute="width" secondItem="BGD-sd-SQR" secondAttribute="width" id="3Mr-fA-bfF"/>
<constraint firstItem="nLd-BP-JAE" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="4Q7-hr-rqi"/>
@@ -160,12 +166,15 @@
<constraint firstItem="XX4-n6-hCm" firstAttribute="bottom" secondItem="nLd-BP-JAE" secondAttribute="top" id="QO8-nF-xys"/>
<constraint firstItem="XX4-n6-hCm" firstAttribute="width" secondItem="iN0-l3-epB" secondAttribute="width" id="WhE-lH-ZtR"/>
<constraint firstItem="BGD-sd-SQR" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="X14-4s-uGM"/>
<constraint firstItem="gt1-EO-UVY" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="XGH-fF-BKB"/>
<constraint firstAttribute="trailing" secondItem="nLd-BP-JAE" secondAttribute="trailing" id="YAu-gd-ItG"/>
<constraint firstItem="S6r-bo-jxw" firstAttribute="centerX" secondItem="BGD-sd-SQR" secondAttribute="centerX" id="a2s-5o-q2d"/>
<constraint firstItem="gt1-EO-UVY" firstAttribute="bottom" secondItem="nLd-BP-JAE" secondAttribute="bottom" id="acJ-g8-R7x"/>
<constraint firstItem="XX4-n6-hCm" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="csl-KT-4s9"/>
<constraint firstItem="54r-18-K1g" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="ghf-co-a4t"/>
<constraint firstItem="BGD-sd-SQR" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="haP-Kv-OLI"/>
<constraint firstAttribute="bottom" secondItem="nLd-BP-JAE" secondAttribute="bottom" id="kQ6-Cg-FMi"/>
<constraint firstAttribute="trailing" secondItem="gt1-EO-UVY" secondAttribute="trailing" id="qmd-M4-phm"/>
<constraint firstItem="uK2-9a-rZj" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="u8r-eN-1g8"/>
<constraint firstItem="S6r-bo-jxw" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="xYa-gT-4x0"/>
<constraint firstItem="uK2-9a-rZj" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="y6b-JK-CF5"/>
@@ -174,7 +183,7 @@
</view>
</objects>
<resources>
<image name="cancel" width="32" height="32"/>
<image name="jump_to_unread" width="48" height="48"/>
<image name="cancel" width="20" height="20"/>
<image name="jump_to_unread" width="30" height="30"/>
</resources>
</document>