element-ios/Riot/Modules/Home/HomeViewController.m

1020 lines
39 KiB
Objective-C

/*
Copyright 2024 New Vector Ltd.
Copyright 2017 Vector Creations Ltd
Copyright 2015 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
#import "HomeViewController.h"
#import "GeneratedInterface-Swift.h"
#import "RecentsDataSource.h"
#import "TableViewCellWithCollectionView.h"
#import "RoomCollectionViewCell.h"
#import "MXRoom+Riot.h"
@interface HomeViewController () <SecureBackupSetupCoordinatorBridgePresenterDelegate, SpaceMembersCoordinatorBridgePresenterDelegate>
{
RecentsDataSource *recentsDataSource;
// Room edition
NSInteger selectedSection;
NSString *selectedRoomId;
UISwipeGestureRecognizer *horizontalSwipeGestureRecognizer;
UISwipeGestureRecognizer *verticalSwipeGestureRecognizer;
// The content offset of the collection in which the edited room is displayed.
// We store this value to prevent the collection view from scrolling to the beginning (observed on iOS < 10).
CGFloat selectedCollectionViewContentOffset;
}
@property (nonatomic, strong) SecureBackupSetupCoordinatorBridgePresenter *secureBackupSetupCoordinatorBridgePresenter;
@property (nonatomic, strong) SecureBackupBannerCell *secureBackupBannerPrototypeCell;
@property (nonatomic, strong) CrossSigningSetupBannerCell *keyVerificationSetupBannerPrototypeCell;
@property (nonatomic, strong) CrossSigningSetupCoordinatorBridgePresenter *crossSigningSetupCoordinatorBridgePresenter;
@property (nonatomic, assign, readwrite) BOOL roomListDataReady;
@property (nonatomic, strong) MXThrottler *collectionViewPaginationThrottler;
@property(nonatomic) SpaceMembersCoordinatorBridgePresenter *spaceMembersCoordinatorBridgePresenter;
@end
@implementation HomeViewController
+ (instancetype)instantiate
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];
HomeViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:@"HomeViewController"];
return viewController;
}
- (void)finalizeInit
{
[super finalizeInit];
selectedSection = -1;
selectedRoomId = nil;
selectedCollectionViewContentOffset = -1;
self.screenTracker = [[AnalyticsScreenTracker alloc] initWithScreen:AnalyticsScreenHome];
self.collectionViewPaginationThrottler = [[MXThrottler alloc] initWithMinimumDelay:0.1];
}
- (void)viewDidLoad
{
[super viewDidLoad];
if (!BuildSettings.newAppLayoutEnabled)
{
[self.tabBarController vc_setLargeTitleDisplayMode:UINavigationItemLargeTitleDisplayModeNever];
}
self.roomListDataReady = NO;
self.view.accessibilityIdentifier = @"HomeVCView";
self.recentsTableView.accessibilityIdentifier = @"HomeVCTableView";
// Tag the recents table with the its recents data source mode.
// This will be used by the shared RecentsDataSource instance for sanity checks (see UITableViewDataSource methods).
self.recentsTableView.tag = self.recentsDataSourceMode;
self.recentsTableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
// Add the (+) button programmatically
[self addFabButton];
// Register table view cells used for rooms collection.
[self registerCellsWithCollectionViews];
// Change the table data source. It must be the home view controller itself.
self.recentsTableView.dataSource = self;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (!BuildSettings.newAppLayoutEnabled)
{
[ThemeService.shared.theme applyStyleOnNavigationBar:[AppDelegate theDelegate].masterTabBarController.navigationController.navigationBar];
[AppDelegate theDelegate].masterTabBarController.tabBar.tintColor = ThemeService.shared.theme.tintColor;
}
if (recentsDataSource.recentsDataSourceMode != self.recentsDataSourceMode)
{
// Take the lead on the shared data source.
[recentsDataSource setDelegate:self andRecentsDataSourceMode:self.recentsDataSourceMode];
// Reset filtering on the shared data source when switching tabs
[recentsDataSource searchWithPatterns:nil];
[self.recentsSearchBar setText:nil];
}
}
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
{
if (selectedRoomId)
{
// Cancel room edition in case of device screen rotation.
[self cancelEditionMode:YES];
}
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}
- (void)destroy
{
[super destroy];
}
- (SecureBackupBannerCell *)secureBackupBannerPrototypeCell
{
if (!_secureBackupBannerPrototypeCell)
{
_secureBackupBannerPrototypeCell = [self.recentsTableView dequeueReusableCellWithIdentifier:SecureBackupBannerCell.defaultReuseIdentifier];
}
return _secureBackupBannerPrototypeCell;
}
- (CrossSigningSetupBannerCell *)keyVerificationSetupBannerPrototypeCell
{
if (!_keyVerificationSetupBannerPrototypeCell)
{
_keyVerificationSetupBannerPrototypeCell = [self.recentsTableView dequeueReusableCellWithIdentifier:CrossSigningSetupBannerCell.defaultReuseIdentifier];
}
return _keyVerificationSetupBannerPrototypeCell;
}
- (void)presentSecureBackupSetup
{
SecureBackupSetupCoordinatorBridgePresenter *keyBackupSetupCoordinatorBridgePresenter = [[SecureBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession allowOverwrite:NO];
keyBackupSetupCoordinatorBridgePresenter.delegate = self;
[keyBackupSetupCoordinatorBridgePresenter presentFrom:self animated:YES];
self.secureBackupSetupCoordinatorBridgePresenter = keyBackupSetupCoordinatorBridgePresenter;
}
- (void)addFabButton
{
plusButtonImageView = [self vc_addFABWithImage:AssetImages.plusFloatingAction.image
target:self
action:@selector(onPlusButtonPressed)];
}
- (RecentsDataSourceMode)recentsDataSourceMode
{
return RecentsDataSourceModeHome;
}
#pragma mark - Override RecentsViewController
- (void)displayList:(MXKRecentsDataSource *)listDataSource
{
[super displayList:listDataSource];
// Change the table data source. It must be the home view controller itself.
self.recentsTableView.dataSource = self;
// Keep a ref on the recents data source
if ([listDataSource isKindOfClass:RecentsDataSource.class])
{
recentsDataSource = (RecentsDataSource*)listDataSource;
}
}
- (void)refreshCurrentSelectedCell:(BOOL)forceVisible
{
// Check whether the recents data source is correctly configured.
if (recentsDataSource.recentsDataSourceMode != self.recentsDataSourceMode)
{
return;
}
// TODO: refreshCurrentSelectedCell
//[super refreshCurrentSelectedCell:forceVisible];
}
- (void)didTapOnSectionHeader:(UIGestureRecognizer*)gestureRecognizer
{
UIView *view = gestureRecognizer.view;
NSInteger section = view.tag;
if (selectedRoomId)
{
[self cancelEditionMode:YES];
}
// Scroll to the top this section
if ([self.recentsTableView numberOfRowsInSection:section] > 0)
{
[self.recentsTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
// Scroll to the beginning the corresponding rooms collection.
UITableViewCell *firstSectionCell = [self.recentsTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
if (firstSectionCell && [firstSectionCell isKindOfClass:TableViewCellWithCollectionView.class])
{
TableViewCellWithCollectionView *tableViewCell = (TableViewCellWithCollectionView*)firstSectionCell;
if ([tableViewCell.collectionView numberOfItemsInSection:0] > 0)
{
[tableViewCell.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0] atScrollPosition:UICollectionViewScrollPositionLeft animated:YES];
}
}
}
- (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;
if (selectedCollectionViewContentOffset == -1)
{
selectedSection = -1;
}
// Else, do not reset the selectedSection here,
// it is used during the table refresh to apply the original collection view offset.
// Remove existing gesture recognizers
[self.recentsTableView removeGestureRecognizer:horizontalSwipeGestureRecognizer];
horizontalSwipeGestureRecognizer = nil;
[self.recentsTableView removeGestureRecognizer:verticalSwipeGestureRecognizer];
verticalSwipeGestureRecognizer = nil;
self.recentsTableView.scrollEnabled = YES;
[self refreshRecentsTable];
}
}
- (void)onMatrixSessionChange
{
[super onMatrixSessionChange];
[self updateEmptyView];
}
- (void)startChat {
if (recentsDataSource.currentSpace)
{
self.spaceMembersCoordinatorBridgePresenter = [[SpaceMembersCoordinatorBridgePresenter alloc] initWithUserSessionsService:[UserSessionsService shared] session:self.mainSession spaceId:self.dataSource.currentSpace.spaceId];
self.spaceMembersCoordinatorBridgePresenter.delegate = self;
[self.spaceMembersCoordinatorBridgePresenter presentFrom:self animated:YES];
}
else
{
[super startChat];
}
}
- (void)createNewRoom
{
if (recentsDataSource.currentSpace) {
[recentsDataSource.currentSpace canAddRoomWithCompletion:^(BOOL canAddRoom) {
if (canAddRoom) {
[super createNewRoom];
} else {
[[AppDelegate theDelegate] showAlertWithTitle:[VectorL10n roomRecentsCreateEmptyRoom]
message:[VectorL10n spacesAddRoomMissingPermissionMessage]];
}
}];
} else {
[super createNewRoom];
}
}
#pragma mark - UITableViewDataSource
// Table view cells on the home screen contain nested collection views with their own data source and state.
// In order to preserve properties such as content offset of each collection view, the parent cells must
// be directly associated with each section, so that when getting dequed by the table view, the correct cell
// is reused, rather than cells getting randomly swapped around.
- (void)registerCellsWithCollectionViews
{
for (NSNumber *section in self.sections) {
NSString *cellIdentifier = [self cellIdentifierForSectionType:section.integerValue];
[self.recentsTableView registerClass:TableViewCellWithCollectionView.class forCellReuseIdentifier:cellIdentifier];
}
}
- (NSArray<NSNumber *> *)sections
{
return @[
@(RecentsDataSourceSectionTypeDirectory),
@(RecentsDataSourceSectionTypeInvites),
@(RecentsDataSourceSectionTypeFavorites),
@(RecentsDataSourceSectionTypePeople),
@(RecentsDataSourceSectionTypeConversation),
@(RecentsDataSourceSectionTypeLowPriority),
@(RecentsDataSourceSectionTypeServerNotice),
@(RecentsDataSourceSectionTypeSuggestedRooms),
@(RecentsDataSourceSectionTypeBreadcrumbs)
];
}
- (NSString *)cellIdentifierForSectionType:(RecentsDataSourceSectionType)sectionType
{
// Create cell identifier unique to each semantic section, e.g. 'favorites' will have different cell
// identifier to 'conversations'.
return [NSString stringWithFormat:@"%@-%ld", TableViewCellWithCollectionView.defaultReuseIdentifier, sectionType];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the actual number of sections prepared in recents dataSource.
return [recentsDataSource numberOfSectionsInTableView:tableView];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Edit the potential selected room (see `onCollectionViewCellLongPress`).
editedRoomId = selectedRoomId;
if ([recentsDataSource isSectionShrinkedAt:section])
{
return 0;
}
else
{
// Each rooms section is represented by only one collection view except for the all chats section.
NSInteger index = [recentsDataSource.sections sectionIndexForSectionType:RecentsDataSourceSectionTypeAllChats];
if (section == index)
{
return [self.dataSource tableView:tableView numberOfRowsInSection:section];
}
return 1;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger index = [recentsDataSource.sections sectionIndexForSectionType:RecentsDataSourceSectionTypeAllChats];
if (indexPath.section == index)
{
return [self.dataSource tableView:tableView cellForRowAtIndexPath:indexPath];
}
RecentsDataSourceSectionType sectionType = [recentsDataSource.sections sectionTypeForSectionIndex:indexPath.section];
if ((sectionType == RecentsDataSourceSectionTypeConversation && !recentsDataSource.recentsListService.conversationRoomListData.counts.numberOfRooms)
|| (sectionType == RecentsDataSourceSectionTypePeople && !recentsDataSource.recentsListService.peopleRoomListData.counts.numberOfRooms)
|| (sectionType == RecentsDataSourceSectionTypeSecureBackupBanner)
|| (sectionType == RecentsDataSourceSectionTypeCrossSigningBanner)
)
{
return [recentsDataSource tableView:tableView cellForRowAtIndexPath:indexPath];
}
NSString *cellIdentifier = [self cellIdentifierForSectionType:sectionType];
TableViewCellWithCollectionView *tableViewCell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
tableViewCell.collectionView.tag = indexPath.section;
[tableViewCell.collectionView registerClass:RoomCollectionViewCell.class forCellWithReuseIdentifier:RoomCollectionViewCell.defaultReuseIdentifier];
tableViewCell.collectionView.delegate = self;
tableViewCell.collectionView.dataSource = self;
tableViewCell.selectionStyle = UITableViewCellSelectionStyleNone;
if (editedRoomId)
{
UIColor *selectedColor = ThemeService.shared.theme.tintColor;
UIColor *unselectedColor = ThemeService.shared.theme.tabBarUnselectedItemTintColor;
// Disable collection scrolling during edition
tableViewCell.collectionView.scrollEnabled = NO;
if (indexPath.section == selectedSection)
{
// Show edition menu
tableViewCell.editionViewHeightConstraint.constant = 60;
tableViewCell.editionViewBottomConstraint.constant = 5;
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 = AssetImages.roomActionDirectChat.image;
tableViewCell.directChatImageView.tintColor = room.isDirect ? selectedColor : unselectedColor;
tableViewCell.notificationsButton.tag = room.isMute || room.isMentionsOnly;
[tableViewCell.notificationsButton addTarget:self action:@selector(onNotificationsButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
if ([BuildSettings showNotificationsV2] && tableViewCell.notificationsButton.tag)
{
tableViewCell.notificationsImageView.image = AssetImages.roomActionNotificationMuted.image;
}
else
{
tableViewCell.notificationsImageView.image = AssetImages.roomActionNotification.image;
}
tableViewCell.notificationsImageView.tintColor = tableViewCell.notificationsButton.tag ? unselectedColor : selectedColor;
// Get the room tag (use only the first one).
MXRoomTag* currentTag = nil;
if (room.accountData.tags)
{
NSArray<MXRoomTag*>* tags = room.accountData.tags.allValues;
if (tags.count)
{
currentTag = tags[0];
}
}
tableViewCell.favouriteButton.tag = (currentTag && [kMXRoomTagFavourite isEqualToString:currentTag.name]);
[tableViewCell.favouriteButton addTarget:self action:@selector(onFavouriteButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
tableViewCell.favouriteImageView.image = AssetImages.roomActionFavourite.image;
tableViewCell.favouriteImageView.tintColor = tableViewCell.favouriteButton.tag ? selectedColor : unselectedColor;
tableViewCell.priorityButton.tag = (currentTag && [kMXRoomTagLowPriority isEqualToString:currentTag.name]);
[tableViewCell.priorityButton addTarget:self action:@selector(onPriorityButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
tableViewCell.priorityImageView.image = tableViewCell.priorityButton.tag ? AssetImages.roomActionPriorityHigh.image : AssetImages.roomActionPriorityLow.image;
tableViewCell.priorityImageView.tintColor = unselectedColor;
[tableViewCell.leaveButton addTarget:self action:@selector(onLeaveButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
tableViewCell.leaveImageView.image = AssetImages.roomActionLeave.image;
tableViewCell.leaveImageView.tintColor = unselectedColor;
}
}
return tableViewCell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger index = [recentsDataSource.sections sectionIndexForSectionType:RecentsDataSourceSectionTypeAllChats];
if (indexPath.section == index)
{
return [self.dataSource tableView:tableView canEditRowAtIndexPath:indexPath];
}
return NO;
}
#pragma mark - UITableView delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger index = [recentsDataSource.sections sectionIndexForSectionType:RecentsDataSourceSectionTypeAllChats];
if (indexPath.section == index)
{
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
RecentsDataSourceSectionType sectionType = [recentsDataSource.sections sectionTypeForSectionIndex:indexPath.section];
if ((sectionType == RecentsDataSourceSectionTypeConversation && !recentsDataSource.recentsListService.conversationRoomListData.counts.numberOfRooms)
|| (sectionType == RecentsDataSourceSectionTypePeople && !recentsDataSource.recentsListService.peopleRoomListData.counts.numberOfRooms))
{
return [recentsDataSource cellHeightAtIndexPath:indexPath];
}
else if (sectionType == RecentsDataSourceSectionTypeSecureBackupBanner || sectionType == RecentsDataSourceSectionTypeCrossSigningBanner)
{
CGFloat height = 0.0;
UITableViewCell *sizingCell;
if (sectionType == RecentsDataSourceSectionTypeSecureBackupBanner)
{
SecureBackupBannerCell *secureBackupBannerCell = self.secureBackupBannerPrototypeCell;
[secureBackupBannerCell configureFor:recentsDataSource.secureBackupBannerDisplay];
sizingCell = secureBackupBannerCell;
}
else if (sectionType == RecentsDataSourceSectionTypeCrossSigningBanner)
{
sizingCell = self.keyVerificationSetupBannerPrototypeCell;
}
[sizingCell layoutIfNeeded];
CGSize fittingSize = UILayoutFittingCompressedSize;
CGFloat tableViewWidth = CGRectGetWidth(tableView.frame);
CGFloat safeAreaWidth = MAX(tableView.safeAreaInsets.left, tableView.safeAreaInsets.right);
fittingSize.width = tableViewWidth - safeAreaWidth;
height = [sizingCell systemLayoutSizeFittingSize:fittingSize withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel].height;
return height;
}
// Retrieve the fixed height of the collection view cell used to display a room.
CGFloat height = [RoomCollectionViewCell defaultCellSize].height + 1;
// Check the conditions to display the edition menu
if (editedRoomId && indexPath.section == selectedSection)
{
// Add the edition view height
height += 65.0;
}
return height;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
// No header in key banner section
RecentsDataSourceSectionType sectionType = [recentsDataSource.sections sectionTypeForSectionIndex:section];
if (sectionType == RecentsDataSourceSectionTypeSecureBackupBanner
|| sectionType == RecentsDataSourceSectionTypeCrossSigningBanner)
{
return 0.0;
}
else
{
return [(RecentsDataSource *)self.dataSource heightForHeaderInSection:section];
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
RecentsDataSourceSectionType sectionType = [recentsDataSource.sections sectionTypeForSectionIndex:indexPath.section];
if (sectionType == RecentsDataSourceSectionTypeSecureBackupBanner)
{
switch (recentsDataSource.secureBackupBannerDisplay) {
case SecureBackupBannerDisplaySetup:
[self presentSecureBackupSetup];
break;
default:
break;
}
}
else if (sectionType == RecentsDataSourceSectionTypeCrossSigningBanner)
{
[self showCrossSigningSetup];
}
else if (sectionType == RecentsDataSourceSectionTypeAllChats)
{
[super tableView:tableView didSelectRowAtIndexPath:indexPath];
}
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [recentsDataSource tableView:self.recentsTableView numberOfRowsInSection:collectionView.tag];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
RoomCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:RoomCollectionViewCell.defaultReuseIdentifier
forIndexPath:indexPath];
id<MXKRecentCellDataStoring> cellData = [recentsDataSource cellDataAtIndexPath:[NSIndexPath indexPathForRow:indexPath.item inSection:collectionView.tag]];
if (cellData)
{
[cell render:cellData];
cell.tag = indexPath.item;
cell.collectionViewTag = collectionView.tag;
if (selectedCollectionViewContentOffset != -1 && collectionView.tag == selectedSection)
{
if (collectionView.contentOffset.x != selectedCollectionViewContentOffset)
{
// Force here the content offset of the collection in which the edited cell is displayed.
// Indeed because of the table view cell height change the collection view scrolls at the beginning by default (on iOS < 10).
collectionView.contentOffset = CGPointMake(selectedCollectionViewContentOffset, 0) ;
}
if (editedRoomId)
{
// Scroll the collection view in order to fully display the edited cell.
NSIndexPath *indexPath = [self.dataSource cellIndexPathWithRoomId:editedRoomId andMatrixSession:self.mainSession];
indexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:0];
[collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionNone animated:YES];
selectedCollectionViewContentOffset = collectionView.contentOffset.x;
}
else
{
// The edition mode is left now, remove the last stored values.
selectedSection = -1;
selectedCollectionViewContentOffset = -1;
}
}
// Edition mode?
if (editedRoomId)
{
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onCollectionViewCellTap:)];
[cell addGestureRecognizer:tapGesture];
if ([cellData.roomIdentifier isEqualToString:editedRoomId])
{
cell.editionArrowView.hidden = NO;
}
}
else
{
if (@available(iOS 13.0, *))
{
// Use context menu instead
}
else
{
// Add long tap gesture recognizer.
UILongPressGestureRecognizer *cellLongPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onCollectionViewCellLongPress:)];
[cell addGestureRecognizer:cellLongPressGesture];
}
}
}
cell.backgroundColor = ThemeService.shared.theme.backgroundColor;
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger collectionViewSection = indexPath.section;
if (collectionView.numberOfSections <= collectionViewSection)
{
return;
}
NSInteger numberOfItemsInSection = [collectionView numberOfItemsInSection:collectionViewSection];
if (indexPath.item != numberOfItemsInSection - 1)
{
return;
}
[self.collectionViewPaginationThrottler throttle:^{
NSInteger tableViewSection = collectionView.tag;
[self->recentsDataSource paginateInSection:tableViewSection];
}];
}
#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (self.delegate)
{
RoomCollectionViewCell *roomCollectionViewCell = (RoomCollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath];
id<MXKRecentCellDataStoring> renderedCellData = (id<MXKRecentCellDataStoring>)roomCollectionViewCell.renderedCellData;
if (renderedCellData.isSuggestedRoom)
{
[self.delegate recentListViewController:self
didSelectSuggestedRoom:renderedCellData.roomSummary.spaceChildInfo
from:roomCollectionViewCell];
}
else
{
[self.delegate recentListViewController:self
didSelectRoom:renderedCellData.roomIdentifier
inMatrixSession:renderedCellData.mxSession];
}
}
// Hide the keyboard when user select a room
// do not hide the searchBar until the view controller disappear
// on tablets / iphone 6+, the user could expect to search again while looking at a room
[self.recentsSearchBar resignFirstResponder];
}
#pragma mark - UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
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.summary.membership == MXMembershipInvite)
{
return;
}
// Store the identifier of the room related to the edited cell.
selectedRoomId = room.roomId;
// Store the concerned section
selectedCollectionViewContentOffset = -1;
selectedSection = selectedCell.collectionViewTag;
// Store the current content offset of the selected collection before refreshing.
NSIndexPath *tableViewCellIndexPath = [NSIndexPath indexPathForRow:0 inSection:selectedSection];
TableViewCellWithCollectionView *tableViewCellWithCollectionView = [self.recentsTableView cellForRowAtIndexPath:tableViewCellIndexPath];
CGFloat selectedCollectionViewContentOffsetCopy = tableViewCellWithCollectionView.collectionView.contentOffset.x;
[self refreshRecentsTable];
// Make visible the edited cell
tableViewCellWithCollectionView = [self.recentsTableView cellForRowAtIndexPath:tableViewCellIndexPath];
NSIndexPath *collectionViewCellIndexPath = [self.dataSource cellIndexPathWithRoomId:selectedRoomId andMatrixSession:room.mxSession];
collectionViewCellIndexPath = [NSIndexPath indexPathForItem:collectionViewCellIndexPath.item inSection:0];
UICollectionViewCell *roomCollectionViewCell = [tableViewCellWithCollectionView.collectionView cellForItemAtIndexPath:collectionViewCellIndexPath];
if (roomCollectionViewCell)
{
[tableViewCellWithCollectionView.collectionView scrollRectToVisible:roomCollectionViewCell.frame animated:YES];
}
else
{
// On iOS < 10, the collection view scrolls to the beginning during the table refresh.
// We store here the actual content offset, used during the collection view loading.
selectedCollectionViewContentOffset = selectedCollectionViewContentOffsetCopy;
}
[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
{
UIButton *button = (UIButton*)sender;
[self makeDirectEditedRoom:!button.tag];
}
- (IBAction)onNotificationsButtonPressed:(id)sender
{
if ([BuildSettings showNotificationsV2])
{
[self changeEditedRoomNotificationSettings];
}
else
{
UIButton *button = (UIButton*)sender;
[self muteEditedRoomNotifications:!button.tag];
}
}
- (IBAction)onFavouriteButtonPressed:(id)sender
{
UIButton *button = (UIButton*)sender;
if (button.tag)
{
[self updateEditedRoomTag:nil];
}
else
{
[self updateEditedRoomTag:kMXRoomTagFavourite];
}
}
- (IBAction)onPriorityButtonPressed:(id)sender
{
UIButton *button = (UIButton*)sender;
if (button.tag)
{
[self updateEditedRoomTag:nil];
}
else
{
[self updateEditedRoomTag:kMXRoomTagLowPriority];
}
}
- (IBAction)onLeaveButtonPressed:(id)sender
{
[self leaveEditedRoom];
}
#pragma mark - SecureBackupSetupCoordinatorBridgePresenterDelegate
- (void)secureBackupSetupCoordinatorBridgePresenterDelegateDidComplete:(SecureBackupSetupCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
[self.secureBackupSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
self.secureBackupSetupCoordinatorBridgePresenter = nil;
}
- (void)secureBackupSetupCoordinatorBridgePresenterDelegateDidCancel:(SecureBackupSetupCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
[self.secureBackupSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
self.secureBackupSetupCoordinatorBridgePresenter = nil;
}
#pragma mark - Cross-signing setup
- (void)showCrossSigningSetup
{
[self setupCrossSigningWithTitle:[VectorL10n crossSigningSetupBannerTitle] message:[VectorL10n securitySettingsUserPasswordDescription] success:^{
} failure:^(NSError *error) {
}];
}
- (void)setupCrossSigningWithTitle:(NSString*)title
message:(NSString*)message
success:(void (^)(void))success
failure:(void (^)(NSError *error))failure
{
[self startActivityIndicator];
self.view.userInteractionEnabled = NO;
MXWeakify(self);
void (^animationCompletion)(void) = ^void () {
MXStrongifyAndReturnIfNil(self);
[self stopActivityIndicator];
self.view.userInteractionEnabled = YES;
[self.crossSigningSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:^{}];
self.crossSigningSetupCoordinatorBridgePresenter = nil;
};
CrossSigningSetupCoordinatorBridgePresenter *crossSigningSetupCoordinatorBridgePresenter = [[CrossSigningSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession];
[crossSigningSetupCoordinatorBridgePresenter presentWith:title
message:message
from:self
animated:YES
success:^{
animationCompletion();
// TODO: Remove this line and refresh key verification setup banner by listening to a local notification cross-signing state change (Add this behavior into the SDK).
[self->recentsDataSource setDelegate:self andRecentsDataSourceMode:self.recentsDataSourceMode];
[self refreshRecentsTable];
success();
} cancel:^{
animationCompletion();
failure(nil);
} failure:^(NSError * _Nonnull error) {
animationCompletion();
[self refreshRecentsTable];
[[AppDelegate theDelegate] showErrorAsAlert:error];
failure(error);
}];
self.crossSigningSetupCoordinatorBridgePresenter = crossSigningSetupCoordinatorBridgePresenter;
}
#pragma mark - Empty view management
- (void)updateEmptyView
{
MXUser *myUser = self.mainSession.myUser;
NSString *displayName = myUser.displayname ?: myUser.userId;
displayName = displayName ?: @"";
NSString *appName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"];
NSString *title = [VectorL10n homeEmptyViewTitle:appName :displayName];
[self.emptyView fillWith:[self emptyViewArtwork]
title:title
informationText:[VectorL10n homeEmptyViewInformation]];
}
- (UIImage*)emptyViewArtwork
{
if (ThemeService.shared.isCurrentThemeDark)
{
return AssetImages.homeEmptyScreenArtworkDark.image;
}
else
{
return AssetImages.homeEmptyScreenArtwork.image;
}
}
- (BOOL)shouldShowEmptyView
{
// Do not present empty screen while searching
if (recentsDataSource.searchPatternsList.count)
{
return NO;
}
// Check if some banners should be displayed
if ([recentsDataSource.sections contains:RecentsDataSourceSectionTypeSecureBackupBanner]
|| [recentsDataSource.sections contains:RecentsDataSourceSectionTypeCrossSigningBanner])
{
return NO;
}
// Otherwise check the number of items to display
return recentsDataSource.totalVisibleItemCount == 0;
}
#pragma mark - SpaceMembersCoordinatorBridgePresenterDelegate
- (void)spaceMembersCoordinatorBridgePresenterDelegateDidComplete:(SpaceMembersCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
self.spaceMembersCoordinatorBridgePresenter = nil;
}];
}
#pragma mark - Context Menu
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point API_AVAILABLE(ios(13.0))
{
RecentsDataSourceSectionType sectionType = [recentsDataSource.sections sectionTypeForSectionIndex:indexPath.section];
if (sectionType == RecentsDataSourceSectionTypeAllChats)
{
return [super tableView:tableView contextMenuConfigurationForRowAtIndexPath:indexPath point:point];
}
return nil;
}
- (UIContextMenuConfiguration *)collectionView:(UICollectionView *)collectionView contextMenuConfigurationForItemAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point API_AVAILABLE(ios(13.0))
{
id<MXKRecentCellDataStoring> cellData = [recentsDataSource cellDataAtIndexPath:[NSIndexPath indexPathForRow:indexPath.item inSection:collectionView.tag]];
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
if (!cellData || !cell)
{
return nil;
}
return [self.contextMenuProvider contextMenuConfigurationWith:cellData from:cell session:self.dataSource.mxSession];
}
- (void)collectionView:(UICollectionView *)collectionView willPerformPreviewActionForMenuWithConfiguration:(UIContextMenuConfiguration *)configuration animator:(id<UIContextMenuInteractionCommitAnimating>)animator API_AVAILABLE(ios(13.0))
{
NSString *roomId = [self.contextMenuProvider roomIdFrom:configuration.identifier];
if (!roomId)
{
self.recentsUpdateEnabled = YES;
return;
}
[animator addCompletion:^{
self.recentsUpdateEnabled = YES;
[self showRoomWithRoomId:roomId inMatrixSession:self.mainSession];
}];
}
- (UITargetedPreview *)collectionView:(UICollectionView *)collectionView previewForDismissingContextMenuWithConfiguration:(UIContextMenuConfiguration *)configuration API_AVAILABLE(ios(13.0))
{
self.recentsUpdateEnabled = YES;
return nil;
}
@end