diff --git a/Riot/ViewController/HomeViewController.m b/Riot/ViewController/HomeViewController.m index 1570e310b..57abf4621 100644 --- a/Riot/ViewController/HomeViewController.m +++ b/Riot/ViewController/HomeViewController.m @@ -24,9 +24,17 @@ #import "TableViewCellWithCollectionView.h" #import "RoomCollectionViewCell.h" +#import "MXRoom+Riot.h" + @interface HomeViewController () { RecentsDataSource *recentsDataSource; + + // Room edition + NSInteger editedSection; + NSString *selectedRoomId; + UISwipeGestureRecognizer *horizontalSwipeGestureRecognizer; + UISwipeGestureRecognizer *verticalSwipeGestureRecognizer; } @end @@ -36,6 +44,9 @@ { [super finalizeInit]; + editedSection = -1; + selectedRoomId = nil; + self.screenName = @"Home"; } @@ -156,6 +167,48 @@ } } +- (void)scrollToTop:(BOOL)animated +{ + if (selectedRoomId) + { + [self cancelEditionMode:YES]; + } + + [super scrollToTop:animated]; +} + +- (void)onPlusButtonPressed +{ + if (selectedRoomId) + { + [self cancelEditionMode:YES]; + } + + [super onPlusButtonPressed]; +} + +- (void)cancelEditionMode:(BOOL)forceRefresh +{ + if (selectedRoomId) + { + // Ignore forceRefresh, a table refresh is forced at the end. + [super cancelEditionMode:NO]; + + editedRoomId = selectedRoomId = nil; + editedSection = -1; + + // Remove existing gesture recognizers + [self.recentsTableView removeGestureRecognizer:horizontalSwipeGestureRecognizer]; + horizontalSwipeGestureRecognizer = nil; + [self.recentsTableView removeGestureRecognizer:verticalSwipeGestureRecognizer]; + verticalSwipeGestureRecognizer = nil; + + self.recentsTableView.scrollEnabled = YES; + + [self refreshRecentsTable]; + } +} + #pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView @@ -166,6 +219,9 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + // Edit the potential selected room (see `onCollectionViewCellLongPress`). + editedRoomId = selectedRoomId; + // Each rooms section is represented by only one collection view. return 1; } @@ -185,6 +241,52 @@ tableViewCell.collectionView.dataSource = self; tableViewCell.selectionStyle = UITableViewCellSelectionStyleNone; + if (editedSection != -1 && editedRoomId) + { + // Disable collection scrolling during edition + tableViewCell.collectionView.scrollEnabled = NO; + + if (indexPath.section == editedSection) + { + // Show edition menu + tableViewCell.editionViewHeightConstraint.constant = 65; + tableViewCell.editionView.hidden = NO; + + MXRoom *room = [self.mainSession roomWithRoomId:editedRoomId]; + + // Update the edition menu content (Use the button tag to store the current value). + tableViewCell.directChatButton.tag = room.isDirect; + [tableViewCell.directChatButton addTarget:self action:@selector(onDirectChatButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + tableViewCell.directChatImageView.image = tableViewCell.directChatButton.tag ? [UIImage imageNamed:@"directChatOff"] : [UIImage imageNamed:@"directChatOn"]; + + tableViewCell.notificationsButton.tag = room.isMute || room.isMentionsOnly; + [tableViewCell.notificationsButton addTarget:self action:@selector(onNotificationsButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + tableViewCell.notificationsImageView.image = tableViewCell.notificationsButton.tag ? [UIImage imageNamed:@"notifications"] : [UIImage imageNamed:@"notificationsOff"]; + + // Get the room tag (use only the first one). + MXRoomTag* currentTag = nil; + if (room.accountData.tags) + { + NSArray* tags = room.accountData.tags.allValues; + if (tags.count) + { + currentTag = [tags objectAtIndex:0]; + } + } + + tableViewCell.favouriteButton.tag = (currentTag && [kMXRoomTagFavourite isEqualToString:currentTag.name]); + [tableViewCell.favouriteButton addTarget:self action:@selector(onFavouriteButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + tableViewCell.favouriteImageView.image = tableViewCell.favouriteButton.tag ? [UIImage imageNamed:@"favouriteOff"] : [UIImage imageNamed:@"favourite"]; + + tableViewCell.priorityButton.tag = (currentTag && [kMXRoomTagLowPriority isEqualToString:currentTag.name]); + [tableViewCell.priorityButton addTarget:self action:@selector(onPriorityButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + tableViewCell.priorityImageView.image = tableViewCell.priorityButton.tag ? [UIImage imageNamed:@"priorityHigh"] : [UIImage imageNamed:@"priorityLow"]; + + [tableViewCell.leaveButton addTarget:self action:@selector(onLeaveButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + tableViewCell.leaveImageView.image = [UIImage imageNamed:@"leave"]; + } + } + return tableViewCell; } @@ -203,8 +305,16 @@ return [recentsDataSource cellHeightAtIndexPath:indexPath]; } - // Return the fixed height of the collection view cell used to display a room. - return [RoomCollectionViewCell defaultCellSize].height; + if (indexPath.section != editedSection) + { + // Return the fixed height of the collection view cell used to display a room. + return [RoomCollectionViewCell defaultCellSize].height; + } + else + { + // Add the edition view height + return [RoomCollectionViewCell defaultCellSize].height + 65.0; + } } #pragma mark - UICollectionViewDataSource @@ -225,10 +335,25 @@ { [cell render:cellData]; cell.tag = indexPath.item; + cell.collectionViewTag = collectionView.tag; - //TODO: add long tap gesture recognizer. -// UILongPressGestureRecognizer *cellLongPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onCollectionViewCellLongPress:)]; -// [cell addGestureRecognizer:cellLongPressGesture]; + // Edition mode? + if (editedRoomId) + { + UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onCollectionViewCellTap:)]; + [cell addGestureRecognizer:tapGesture]; + + if ([cellData.roomSummary.roomId isEqualToString:editedRoomId]) + { + cell.editionArrowView.hidden = NO; + } + } + else + { + // Add long tap gesture recognizer. + UILongPressGestureRecognizer *cellLongPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onCollectionViewCellLongPress:)]; + [cell addGestureRecognizer:cellLongPressGesture]; + } } return cell; @@ -260,4 +385,138 @@ return [RoomCollectionViewCell defaultCellSize]; } +#pragma mark - Gesture Recognizer + +- (void)onCollectionViewCellLongPress:(UIGestureRecognizer*)gestureRecognizer +{ + RoomCollectionViewCell *selectedCell; + + if (gestureRecognizer.state == UIGestureRecognizerStateBegan) + { + UIView *view = gestureRecognizer.view; + if ([view isKindOfClass:[RoomCollectionViewCell class]]) + { + selectedCell = (RoomCollectionViewCell*)view; + + MXRoom* room = [self.dataSource getRoomAtIndexPath:[NSIndexPath indexPathForRow:selectedCell.tag inSection:selectedCell.collectionViewTag]]; + + if (room) + { + // Display no action for the invited room + if (room.state.membership == MXMembershipInvite) + { + return; + } + + // Store the identifier of the room related to the edited cell. + selectedRoomId = room.state.roomId; + // Store the concerned section + editedSection = selectedCell.collectionViewTag; + + [self refreshRecentsTable]; + + // Make visible the edited cell + TableViewCellWithCollectionView *tableViewCellWithCollectionView = [self.recentsTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:editedSection]]; + NSIndexPath *indexPath = [self.dataSource cellIndexPathWithRoomId:selectedRoomId andMatrixSession:room.mxSession]; + indexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:0]; + UICollectionViewCell *roomCollectionViewCell = [tableViewCellWithCollectionView.collectionView cellForItemAtIndexPath:indexPath]; + [tableViewCellWithCollectionView.collectionView scrollRectToVisible:roomCollectionViewCell.frame animated:YES]; + [self.recentsTableView scrollRectToVisible:tableViewCellWithCollectionView.frame animated:YES]; + + // Disable table view scrolling, and defined the swipe gesture recognizers used to cancel the edition mode + self.recentsTableView.scrollEnabled = NO; + horizontalSwipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(onTableViewSwipe:)]; + [horizontalSwipeGestureRecognizer setDirection:(UISwipeGestureRecognizerDirectionLeft | UISwipeGestureRecognizerDirectionRight)]; + [self.recentsTableView addGestureRecognizer:horizontalSwipeGestureRecognizer]; + verticalSwipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(onTableViewSwipe:)]; + [verticalSwipeGestureRecognizer setDirection:(UISwipeGestureRecognizerDirectionUp | UISwipeGestureRecognizerDirectionDown)]; + [self.recentsTableView addGestureRecognizer:verticalSwipeGestureRecognizer]; + } + } + } +} + +- (void)onCollectionViewCellTap:(UIGestureRecognizer*)gestureRecognizer +{ + [self cancelEditionMode:YES]; +} + +- (void)onTableViewSwipe:(UIGestureRecognizer*)gestureRecognizer +{ + [self cancelEditionMode:YES]; +} + +#pragma mark - Action + +- (IBAction)onDirectChatButtonPressed:(id)sender +{ + if (editedRoomId) + { + MXRoom *room = [self.mainSession roomWithRoomId:editedRoomId]; + if (room) + { + UIButton *button = (UIButton*)sender; + [self makeDirectEditedRoom:!button.tag]; + } + } +} + +- (IBAction)onNotificationsButtonPressed:(id)sender +{ + if (editedRoomId) + { + MXRoom *room = [self.mainSession roomWithRoomId:editedRoomId]; + if (room) + { + UIButton *button = (UIButton*)sender; + [self muteEditedRoomNotifications:!button.tag]; + } + } +} + +- (IBAction)onFavouriteButtonPressed:(id)sender +{ + if (editedRoomId) + { + MXRoom *room = [self.mainSession roomWithRoomId:editedRoomId]; + if (room) + { + UIButton *button = (UIButton*)sender; + if (button.tag) + { + [self updateEditedRoomTag:nil]; + } + else + { + [self updateEditedRoomTag:kMXRoomTagFavourite]; + } + } + } +} + +- (IBAction)onPriorityButtonPressed:(id)sender +{ + if (editedRoomId) + { + MXRoom *room = [self.mainSession roomWithRoomId:editedRoomId]; + if (room) + { + UIButton *button = (UIButton*)sender; + if (button.tag) + { + [self updateEditedRoomTag:nil]; + } + else + { + [self updateEditedRoomTag:kMXRoomTagLowPriority]; + } + } + } +} + +- (IBAction)onLeaveButtonPressed:(id)sender +{ + [self leaveEditedRoom]; +} + @end diff --git a/Riot/ViewController/RecentsViewController.h b/Riot/ViewController/RecentsViewController.h index 859b962b4..648e0f675 100644 --- a/Riot/ViewController/RecentsViewController.h +++ b/Riot/ViewController/RecentsViewController.h @@ -20,6 +20,11 @@ @interface RecentsViewController : MXKRecentListViewController { @protected + /** + The room identifier related to the cell which is in editing mode (if any). + */ + NSString *editedRoomId; + /** The image view of the (+) button. */ @@ -106,6 +111,12 @@ */ - (void)refreshCurrentSelectedCell:(BOOL)forceVisible; +/** + Leave the edition mode + + @param forceRefresh force table view refresh + */ +- (void)cancelEditionMode:(BOOL)forceRefresh; #pragma mark - Room handling /** @@ -129,6 +140,33 @@ */ - (void)joinARoom; +/** + Leave the selected room. + */ +- (void)leaveEditedRoom; + +/** + Update the selected room tag. + */ +- (void)updateEditedRoomTag:(NSString*)tag; + +/** + Enable/disable the direct flag of the selected room. + */ +- (void)makeDirectEditedRoom:(BOOL)isDirect; + +/** + Enable/disable the notifications for the selected room. + */ +- (void)muteEditedRoomNotifications:(BOOL)mute; + +#pragma mark - Scrolling + +/** + Scroll to the top of the recents list. + */ +- (void)scrollToTop:(BOOL)animated; + /** Scroll the next room with missed notifications to the top. diff --git a/Riot/ViewController/RecentsViewController.m b/Riot/ViewController/RecentsViewController.m index 913461394..9bece821c 100644 --- a/Riot/ViewController/RecentsViewController.m +++ b/Riot/ViewController/RecentsViewController.m @@ -35,9 +35,6 @@ @interface RecentsViewController () { - // The room identifier related to the cell which is in editing mode (if any). - NSString *editedRoomId; - // Tell whether a recents refresh is pending (suspended during editing mode). BOOL isRefreshPending; @@ -140,7 +137,7 @@ UIApplicationDidEnterBackgroundNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { // Leave potential editing mode - [self setEditing:NO]; + [self cancelEditionMode:isRefreshPending]; }]; @@ -225,7 +222,7 @@ [super viewWillDisappear:animated]; // Leave potential editing mode - [self setEditing:NO]; + [self cancelEditionMode:NO]; if (kAppDelegateDidTapStatusBarNotificationObserver) { @@ -287,6 +284,8 @@ return; } + isRefreshPending = NO; + if (editedRoomId) { // Check whether the user didn't leave the room @@ -298,13 +297,12 @@ } else { - // Cancel the editing mode - editedRoomId = nil; + // Cancel the editing mode, a new refresh will be triggered. + [self cancelEditionMode:YES]; + return; } } - isRefreshPending = NO; - // Force reset existing sticky headers if any [self resetStickyHeaders]; @@ -382,6 +380,26 @@ } } +- (void)cancelEditionMode:(BOOL)forceRefresh +{ + if (self.recentsTableView.isEditing || self.isEditing) + { + // Leave editing mode first + isRefreshPending = forceRefresh; + [self setEditing:NO]; + } + else + { + // Clean + editedRoomId = nil; + + if (forceRefresh) + { + [self refreshRecentsTable]; + } + } +} + #pragma mark - Sticky Headers - (void)setEnableStickyHeaders:(BOOL)enableStickyHeaders @@ -683,11 +701,6 @@ #pragma mark - Internal methods -- (void)scrollToTop:(BOOL)animated -{ - [self.recentsTableView setContentOffset:CGPointMake(-self.recentsTableView.contentInset.left, -self.recentsTableView.contentInset.top) animated:animated]; -} - -(void)showPublicRoomsDirectory { // Here the recents view controller is displayed inside a unified search view controller. @@ -743,7 +756,7 @@ // Retrieve the invited room MXRoom *invitedRoom = userInfo[kInviteRecentTableViewCellRoomKey]; - [self setEditing:NO]; + [self cancelEditionMode:isRefreshPending]; // Decline the invitation [invitedRoom leave:^{ @@ -893,12 +906,7 @@ - (void)tableView:(UITableView*)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath { - editedRoomId = nil; - - if (isRefreshPending) - { - [self refreshRecentsTable]; - } + [self cancelEditionMode:isRefreshPending]; } - (void)leaveEditedRoom @@ -924,8 +932,7 @@ [self stopActivityIndicator]; // Force table refresh - editedRoomId = nil; - [self refreshRecentsTable]; + [self cancelEditionMode:YES]; } failure:^(NSError *error) { @@ -937,13 +944,13 @@ [self stopActivityIndicator]; // Leave editing mode - [self setEditing:NO]; + [self cancelEditionMode:isRefreshPending]; }]; } else { // Leave editing mode - [self setEditing:NO]; + [self cancelEditionMode:isRefreshPending]; } } } @@ -963,15 +970,14 @@ [self stopActivityIndicator]; // Force table refresh - editedRoomId = nil; - [self refreshRecentsTable]; + [self cancelEditionMode:YES]; }]; } else { // Leave editing mode - [self setEditing:NO]; + [self cancelEditionMode:isRefreshPending]; } } } @@ -991,7 +997,7 @@ [self stopActivityIndicator]; // Leave editing mode - [self setEditing:NO]; + [self cancelEditionMode:isRefreshPending]; } failure:^(NSError *error) { @@ -1004,14 +1010,14 @@ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error]; // Leave editing mode - [self setEditing:NO]; + [self cancelEditionMode:isRefreshPending]; }]; } else { // Leave editing mode - [self setEditing:NO]; + [self cancelEditionMode:isRefreshPending]; } } } @@ -1033,7 +1039,7 @@ [self stopActivityIndicator]; // Leave editing mode - [self setEditing:NO]; + [self cancelEditionMode:isRefreshPending]; }]; } @@ -1044,7 +1050,7 @@ [self stopActivityIndicator]; // Leave editing mode - [self setEditing:NO]; + [self cancelEditionMode:isRefreshPending]; }]; } @@ -1052,7 +1058,7 @@ else { // Leave editing mode - [self setEditing:NO]; + [self cancelEditionMode:isRefreshPending]; } } } @@ -1658,7 +1664,12 @@ [currentAlert showInViewController:self]; } -#pragma mark - Table view scroll handling +#pragma mark - Table view scrolling + +- (void)scrollToTop:(BOOL)animated +{ + [self.recentsTableView setContentOffset:CGPointMake(-self.recentsTableView.contentInset.left, -self.recentsTableView.contentInset.top) animated:animated]; +} - (void)scrollToTheTopTheNextRoomWithMissedNotificationsInSection:(NSInteger)section { diff --git a/Riot/Views/RoomList/RoomCollectionViewCell.h b/Riot/Views/RoomList/RoomCollectionViewCell.h index c3b48a372..e28aa6642 100644 --- a/Riot/Views/RoomList/RoomCollectionViewCell.h +++ b/Riot/Views/RoomList/RoomCollectionViewCell.h @@ -32,6 +32,8 @@ @property (weak, nonatomic) IBOutlet UILabel *roomTitle1; @property (weak, nonatomic) IBOutlet UILabel *roomTitle2; +@property (weak, nonatomic) IBOutlet UIView *editionArrowView; + @property (weak, nonatomic) IBOutlet UIView *directRoomBorderView; @property (weak, nonatomic) IBOutlet MXKImageView *roomAvatar; @property (weak, nonatomic) IBOutlet UIImageView *encryptedRoomIcon; @@ -40,6 +42,10 @@ @property (weak, nonatomic) IBOutlet UIView *missedNotifAndUnreadBadgeBgView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *missedNotifAndUnreadBadgeBgViewWidthConstraint; +@property (nonatomic, readonly) NSString *roomId; + +@property (nonatomic) NSInteger collectionViewTag; // default is -1 + /** The default collection view cell size. */ diff --git a/Riot/Views/RoomList/RoomCollectionViewCell.m b/Riot/Views/RoomList/RoomCollectionViewCell.m index fc6c7e8ed..ea70a7288 100644 --- a/Riot/Views/RoomList/RoomCollectionViewCell.m +++ b/Riot/Views/RoomList/RoomCollectionViewCell.m @@ -53,6 +53,18 @@ // Disable the user interaction on the room avatar. self.roomAvatar.userInteractionEnabled = NO; + + // define arrow mask + CAShapeLayer *arrowMaskLayer = [[CAShapeLayer alloc] init]; + arrowMaskLayer.frame = self.editionArrowView.bounds; + CGSize viewSize = self.editionArrowView.frame.size; + UIBezierPath *path = [[UIBezierPath alloc] init]; + [path moveToPoint:CGPointMake(0, viewSize.height)]; // arrow left bottom point + [path addLineToPoint:CGPointMake(viewSize.width / 2, 0)]; // arrow head + [path addLineToPoint:CGPointMake(viewSize.width, viewSize.height)]; // arrow right bottom point + [path closePath]; // arrow top side + arrowMaskLayer.path = path.CGPath; + self.editionArrowView.layer.mask = arrowMaskLayer; } - (void)layoutSubviews @@ -182,9 +194,21 @@ [self removeGestureRecognizer:self.gestureRecognizers[0]]; } self.tag = -1; + self.collectionViewTag = -1; + + self.editionArrowView.hidden = YES; roomCellData = nil; } +- (NSString*)roomId +{ + if (roomCellData) + { + return roomCellData.roomSummary.roomId; + } + return nil; +} + @end diff --git a/Riot/Views/RoomList/RoomCollectionViewCell.xib b/Riot/Views/RoomList/RoomCollectionViewCell.xib index ee75ecc0a..9ecedd59b 100644 --- a/Riot/Views/RoomList/RoomCollectionViewCell.xib +++ b/Riot/Views/RoomList/RoomCollectionViewCell.xib @@ -82,17 +82,27 @@ + + + @@ -108,6 +118,7 @@ + diff --git a/Riot/Views/TableViewCell/TableViewCellWithCollectionView.h b/Riot/Views/TableViewCell/TableViewCellWithCollectionView.h index d44894c2b..a3a2970f0 100644 --- a/Riot/Views/TableViewCell/TableViewCellWithCollectionView.h +++ b/Riot/Views/TableViewCell/TableViewCellWithCollectionView.h @@ -20,4 +20,17 @@ @property (strong, nonatomic) IBOutlet UICollectionView *collectionView; +@property (strong, nonatomic) IBOutlet UIView *editionView; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *editionViewHeightConstraint; +@property (weak, nonatomic) IBOutlet UIButton *directChatButton; +@property (weak, nonatomic) IBOutlet UIImageView *directChatImageView; +@property (weak, nonatomic) IBOutlet UIButton *notificationsButton; +@property (weak, nonatomic) IBOutlet UIImageView *notificationsImageView; +@property (weak, nonatomic) IBOutlet UIButton *favouriteButton; +@property (weak, nonatomic) IBOutlet UIImageView *favouriteImageView; +@property (weak, nonatomic) IBOutlet UIButton *priorityButton; +@property (weak, nonatomic) IBOutlet UIImageView *priorityImageView; +@property (weak, nonatomic) IBOutlet UIButton *leaveButton; +@property (weak, nonatomic) IBOutlet UIImageView *leaveImageView; + @end diff --git a/Riot/Views/TableViewCell/TableViewCellWithCollectionView.m b/Riot/Views/TableViewCell/TableViewCellWithCollectionView.m index 85cab101b..541442ab0 100644 --- a/Riot/Views/TableViewCell/TableViewCellWithCollectionView.m +++ b/Riot/Views/TableViewCell/TableViewCellWithCollectionView.m @@ -18,6 +18,13 @@ @implementation TableViewCellWithCollectionView +- (void)awakeFromNib +{ + [super awakeFromNib]; + + self.editionViewHeightConstraint.constant = 0; +} + - (void)prepareForReuse { [super prepareForReuse]; @@ -25,6 +32,11 @@ self.collectionView.tag = -1; self.collectionView.dataSource = nil; self.collectionView.delegate = nil; + + self.editionViewHeightConstraint.constant = 0; + self.editionView.hidden = YES; + + self.collectionView.scrollEnabled = YES; } @end diff --git a/Riot/Views/TableViewCell/TableViewCellWithCollectionView.xib b/Riot/Views/TableViewCell/TableViewCellWithCollectionView.xib index 46ff0317f..ae8ae41d8 100644 --- a/Riot/Views/TableViewCell/TableViewCellWithCollectionView.xib +++ b/Riot/Views/TableViewCell/TableViewCellWithCollectionView.xib @@ -1,25 +1,25 @@ - + - + - + - + - + @@ -28,18 +28,133 @@ + - + + + + + + + + + + + + + + + + + + + + + + + +