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

1087 lines
44 KiB
Mathematica
Raw Normal View History

/*
Copyright 2015 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "HomeViewController.h"
#import "GeneratedInterface-Swift.h"
#import "RecentsDataSource.h"
2017-05-05 13:30:35 +00:00
#import "TableViewCellWithCollectionView.h"
#import "RoomCollectionViewCell.h"
#import "MXRoom+Riot.h"
@interface HomeViewController () <SecureBackupSetupCoordinatorBridgePresenterDelegate, SpaceMembersCoordinatorBridgePresenterDelegate>
2017-05-05 13:30:35 +00:00
{
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;
2017-05-05 13:30:35 +00:00
}
2019-01-31 18:52:32 +00:00
2020-06-26 12:01:33 +00:00
@property (nonatomic, strong) SecureBackupSetupCoordinatorBridgePresenter *secureBackupSetupCoordinatorBridgePresenter;
@property (nonatomic, strong) SecureBackupBannerCell *secureBackupBannerPrototypeCell;
2019-01-31 18:52:32 +00:00
2020-06-30 19:46:10 +00:00
@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;
2017-05-05 13:30:35 +00:00
@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.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenHome];
self.collectionViewPaginationThrottler = [[MXThrottler alloc] initWithMinimumDelay:0.1];
}
- (void)viewDidLoad
{
[super viewDidLoad];
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 = RecentsDataSourceModeHome;
2021-09-07 21:29:28 +00:00
self.recentsTableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
// Add the (+) button programmatically
plusButtonImageView = [self vc_addFABWithImage:[UIImage imageNamed:@"plus_floating_action"]
target:self
action:@selector(onPlusButtonPressed)];
2017-05-05 13:30:35 +00:00
// Register table view cell used for rooms collection.
[self.recentsTableView registerClass:TableViewCellWithCollectionView.class forCellReuseIdentifier:TableViewCellWithCollectionView.defaultReuseIdentifier];
2019-01-31 18:52:32 +00:00
2017-05-05 13:30:35 +00:00
// Change the table data source. It must be the home view controller itself.
self.recentsTableView.dataSource = self;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[AppDelegate theDelegate].masterTabBarController.navigationItem.title = [VectorL10n titleHome];
2019-01-11 10:45:27 +00:00
[ThemeService.shared.theme applyStyleOnNavigationBar:[AppDelegate theDelegate].masterTabBarController.navigationController.navigationBar];
2019-01-11 10:45:27 +00:00
[AppDelegate theDelegate].masterTabBarController.tabBar.tintColor = ThemeService.shared.theme.tintColor;
2017-05-05 13:30:35 +00:00
if (recentsDataSource)
{
// Take the lead on the shared data source.
2017-05-05 13:30:35 +00:00
recentsDataSource.areSectionsShrinkable = NO;
[recentsDataSource setDelegate:self andRecentsDataSourceMode:RecentsDataSourceModeHome];
}
[self moveAllCollectionsToLeft];
}
- (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];
}
- (void)moveAllCollectionsToLeft
{
selectedCollectionViewContentOffset = -1;
// Scroll all rooms collections to their beginning
for (NSInteger section = 0; section < [self numberOfSectionsInTableView:self.recentsTableView]; section++)
{
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:NO];
}
}
}
}
2020-06-26 12:01:33 +00:00
- (SecureBackupBannerCell *)secureBackupBannerPrototypeCell
2019-01-31 18:52:32 +00:00
{
2020-06-26 12:01:33 +00:00
if (!_secureBackupBannerPrototypeCell)
2019-01-31 18:52:32 +00:00
{
2020-06-26 12:01:33 +00:00
_secureBackupBannerPrototypeCell = [self.recentsTableView dequeueReusableCellWithIdentifier:SecureBackupBannerCell.defaultReuseIdentifier];
2019-01-31 18:52:32 +00:00
}
2020-06-26 12:01:33 +00:00
return _secureBackupBannerPrototypeCell;
2019-01-31 18:52:32 +00:00
}
2020-06-30 19:46:10 +00:00
- (CrossSigningSetupBannerCell *)keyVerificationSetupBannerPrototypeCell
{
if (!_keyVerificationSetupBannerPrototypeCell)
{
2020-06-30 19:46:10 +00:00
_keyVerificationSetupBannerPrototypeCell = [self.recentsTableView dequeueReusableCellWithIdentifier:CrossSigningSetupBannerCell.defaultReuseIdentifier];
}
return _keyVerificationSetupBannerPrototypeCell;
}
2020-06-25 15:52:36 +00:00
- (void)presentSecureBackupSetup
2019-01-31 18:52:32 +00:00
{
SecureBackupSetupCoordinatorBridgePresenter *keyBackupSetupCoordinatorBridgePresenter = [[SecureBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession allowOverwrite:NO];
2019-01-31 18:52:32 +00:00
keyBackupSetupCoordinatorBridgePresenter.delegate = self;
2019-01-31 18:52:32 +00:00
[keyBackupSetupCoordinatorBridgePresenter presentFrom:self animated:YES];
2020-06-26 11:07:53 +00:00
self.secureBackupSetupCoordinatorBridgePresenter = keyBackupSetupCoordinatorBridgePresenter;
}
2019-01-31 18:52:32 +00:00
#pragma mark - Override RecentsViewController
2017-05-05 13:30:35 +00:00
- (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.
2017-05-05 13:30:35 +00:00
if (recentsDataSource.recentsDataSourceMode != RecentsDataSourceModeHome)
{
2017-05-05 13:30:35 +00:00
return;
}
2017-05-05 13:30:35 +00:00
// 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];
}
if (recentsDataSource.currentSpace != nil)
{
[self showPlusMenuForSpace];
}
else
{
[super onPlusButtonPressed];
}
}
- (void)showPlusMenuForSpace
{
__weak typeof(self) weakSelf = self;
[currentAlert dismissViewControllerAnimated:NO completion:nil];
currentAlert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n spacesExploreRooms]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
[self showRoomDirectory];
}
}]];
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomDetailsPeople]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
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];
}
}]];
[currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel]
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
}
}]];
[currentAlert popoverPresentationController].sourceView = plusButtonImageView;
[currentAlert popoverPresentationController].sourceRect = plusButtonImageView.bounds;
[currentAlert mxk_setAccessibilityIdentifier:@"RecentsVCCreateRoomAlert"];
[self presentViewController:currentAlert animated:YES completion:nil];
}
- (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];
}
2017-05-05 13:30:35 +00:00
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the actual number of sections prepared in recents dataSource.
return [recentsDataSource numberOfSectionsInTableView:tableView];
2017-05-05 13:30:35 +00:00
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Edit the potential selected room (see `onCollectionViewCellLongPress`).
editedRoomId = selectedRoomId;
2017-05-05 13:30:35 +00:00
// Each rooms section is represented by only one collection view.
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
2021-10-04 14:29:04 +00:00
if ((indexPath.section == recentsDataSource.conversationSection && !recentsDataSource.recentsListService.conversationRoomListData.counts.numberOfRooms)
|| (indexPath.section == recentsDataSource.peopleSection && !recentsDataSource.recentsListService.peopleRoomListData.counts.numberOfRooms)
|| (indexPath.section == recentsDataSource.secureBackupBannerSection)
2020-06-30 19:46:10 +00:00
|| (indexPath.section == recentsDataSource.crossSigningBannerSection)
)
2017-05-05 13:30:35 +00:00
{
return [recentsDataSource tableView:tableView cellForRowAtIndexPath:indexPath];
2017-05-05 13:30:35 +00:00
}
TableViewCellWithCollectionView *tableViewCell = [tableView dequeueReusableCellWithIdentifier:TableViewCellWithCollectionView.defaultReuseIdentifier 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;
2017-05-05 13:30:35 +00:00
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 = [UIImage imageNamed:@"room_action_direct_chat"];
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.notificationsImageView.image = tableViewCell.notificationsButton.tag ? [UIImage imageNamed:@"room_action_notification_muted"] : [UIImage imageNamed:@"room_action_notification"];
}
else
{
tableViewCell.notificationsImageView.image = [UIImage imageNamed:@"room_action_notification"];
}
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 = [UIImage imageNamed:@"room_action_favourite"];
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 ? [UIImage imageNamed:@"room_action_priority_high"] : [UIImage imageNamed:@"room_action_priority_low"];
2020-07-08 22:00:02 +00:00
tableViewCell.priorityImageView.tintColor = unselectedColor;
[tableViewCell.leaveButton addTarget:self action:@selector(onLeaveButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
tableViewCell.leaveImageView.image = [UIImage imageNamed:@"room_action_leave"];
2020-07-08 22:00:02 +00:00
tableViewCell.leaveImageView.tintColor = unselectedColor;
}
}
return tableViewCell;
2017-05-05 13:30:35 +00:00
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
return NO;
}
#pragma mark - UITableView delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
2021-10-04 14:29:04 +00:00
if ((indexPath.section == recentsDataSource.conversationSection && !recentsDataSource.recentsListService.conversationRoomListData.counts.numberOfRooms)
|| (indexPath.section == recentsDataSource.peopleSection && !recentsDataSource.recentsListService.peopleRoomListData.counts.numberOfRooms))
2017-05-05 13:30:35 +00:00
{
return [recentsDataSource cellHeightAtIndexPath:indexPath];
2017-05-05 13:30:35 +00:00
}
2020-06-30 19:46:10 +00:00
else if (indexPath.section == recentsDataSource.secureBackupBannerSection || indexPath.section == recentsDataSource.crossSigningBannerSection)
2019-01-31 18:52:32 +00:00
{
CGFloat height = 0.0;
UITableViewCell *sizingCell;
if (indexPath.section == recentsDataSource.secureBackupBannerSection)
{
SecureBackupBannerCell *secureBackupBannerCell = self.secureBackupBannerPrototypeCell;
[secureBackupBannerCell configureFor:recentsDataSource.secureBackupBannerDisplay];
sizingCell = secureBackupBannerCell;
}
2020-06-30 19:46:10 +00:00
else if (indexPath.section == recentsDataSource.crossSigningBannerSection)
{
sizingCell = self.keyVerificationSetupBannerPrototypeCell;
}
[sizingCell layoutIfNeeded];
CGSize fittingSize = UILayoutFittingCompressedSize;
CGFloat tableViewWidth = CGRectGetWidth(tableView.frame);
2020-09-01 12:19:21 +00:00
CGFloat safeAreaWidth = MAX(tableView.safeAreaInsets.left, tableView.safeAreaInsets.right);
2019-01-31 18:52:32 +00:00
fittingSize.width = tableViewWidth - safeAreaWidth;
height = [sizingCell systemLayoutSizeFittingSize:fittingSize withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel].height;
2019-01-31 18:52:32 +00:00
return height;
}
2017-05-05 13:30:35 +00:00
// 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;
2017-05-05 13:30:35 +00:00
}
2019-01-31 18:52:32 +00:00
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
// No header in key banner section
if (section == recentsDataSource.secureBackupBannerSection
2020-06-30 19:46:10 +00:00
|| section == recentsDataSource.crossSigningBannerSection)
2019-01-31 18:52:32 +00:00
{
return 0.0;
}
else
{
return [super tableView:tableView heightForHeaderInSection:section];
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
2020-06-25 15:52:36 +00:00
if (indexPath.section == recentsDataSource.secureBackupBannerSection)
2019-01-31 18:52:32 +00:00
{
2020-06-25 15:52:36 +00:00
switch (recentsDataSource.secureBackupBannerDisplay) {
case SecureBackupBannerDisplaySetup:
[self presentSecureBackupSetup];
2019-01-31 18:52:32 +00:00
break;
2019-02-01 09:15:39 +00:00
default:
break;
2019-01-31 18:52:32 +00:00
}
}
2020-06-30 19:46:10 +00:00
else if (indexPath.section == recentsDataSource.crossSigningBannerSection)
{
2020-06-30 19:46:10 +00:00
[self showCrossSigningSetup];
}
2019-01-31 18:52:32 +00:00
}
2017-05-05 13:30:35 +00:00
#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;
2017-05-05 13:30:35 +00:00
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];
}
}
2017-05-05 13:30:35 +00:00
}
2019-01-11 10:45:27 +00:00
cell.backgroundColor = ThemeService.shared.theme.backgroundColor;
2017-05-05 13:30:35 +00:00
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
[self.collectionViewPaginationThrottler throttle:^{
NSInteger collectionViewSection = indexPath.section;
NSInteger numberOfItemsInSection = [collectionView numberOfItemsInSection:collectionViewSection];
if (collectionView.numberOfSections > collectionViewSection
&& indexPath.item == numberOfItemsInSection - 1)
{
NSInteger tableViewSection = collectionView.tag;
[self->recentsDataSource paginateInSection:tableViewSection];
}
}];
}
2017-05-05 13:30:35 +00:00
#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (self.delegate)
{
RoomCollectionViewCell *roomCollectionViewCell = (RoomCollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath];
2017-05-05 13:30:35 +00:00
id<MXKRecentCellDataStoring> renderedCellData = (id<MXKRecentCellDataStoring>)roomCollectionViewCell.renderedCellData;
if (renderedCellData.isSuggestedRoom)
{
[self.delegate recentListViewController:self
2021-10-01 23:09:36 +00:00
didSelectSuggestedRoom:renderedCellData.roomSummary.spaceChildInfo];
}
else
{
[self.delegate recentListViewController:self
didSelectRoom:renderedCellData.roomIdentifier
inMatrixSession:renderedCellData.mxSession];
}
2017-05-05 13:30:35 +00:00
}
// 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];
}
- (UIContextMenuConfiguration *)collectionView:(UICollectionView *)collectionView contextMenuConfigurationForItemAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point API_AVAILABLE(ios(13.0))
{
UIView *cell = [collectionView cellForItemAtIndexPath:indexPath];
MXRoom *room = [self.dataSource getRoomAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:collectionView.tag]];
NSString *roomId = room.roomId;
MXWeakify(self);
MXWeakify(room);
return [UIContextMenuConfiguration configurationWithIdentifier:roomId previewProvider:^UIViewController * _Nullable {
// Add a preview using the cell's data to prevent the avatar and displayname from changing with a room list update.
return [[ContextMenuSnapshotPreviewViewController alloc] initWithView:cell];
} actionProvider:^UIMenu * _Nullable(NSArray<UIMenuElement *> * _Nonnull suggestedActions) {
MXStrongifyAndReturnValueIfNil(room, nil);
BOOL isDirect = room.isDirect;
2022-01-27 16:43:47 +00:00
UIAction *directChatAction = [UIAction actionWithTitle:isDirect ? VectorL10n.homeContextMenuMakeRoom : VectorL10n.homeContextMenuMakeDm
image:[UIImage systemImageNamed:isDirect ? @"person.crop.circle.badge.xmark" : @"person.circle"]
identifier:nil
handler:^(__kindof UIAction * _Nonnull action) {
MXStrongifyAndReturnIfNil(self);
[self updateRoomWithId:roomId asDirect:!isDirect];
}];
BOOL isMuted = room.isMute || room.isMentionsOnly;
UIImage *notificationsImage;
NSString *notificationsTitle;
if ([BuildSettings showNotificationsV2])
{
2022-01-27 16:43:47 +00:00
notificationsTitle = VectorL10n.homeContextMenuNotifications;
notificationsImage = [UIImage systemImageNamed:@"bell"];
}
else
{
2022-01-27 16:43:47 +00:00
notificationsTitle = isMuted ? VectorL10n.homeContextMenuUnmute : VectorL10n.homeContextMenuMute;
notificationsImage = [UIImage systemImageNamed:isMuted ? @"bell.slash": @"bell"];
}
UIAction *notificationsAction = [UIAction actionWithTitle:notificationsTitle
image:notificationsImage
identifier:nil
handler:^(__kindof UIAction * _Nonnull action) {
MXStrongifyAndReturnIfNil(self);
[self updateRoomWithId:roomId asMuted:!isMuted];
}];
// 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];
}
}
BOOL isFavourite = (currentTag && [kMXRoomTagFavourite isEqualToString:currentTag.name]);
2022-01-27 16:43:47 +00:00
UIAction *favouriteAction = [UIAction actionWithTitle:isFavourite ? VectorL10n.homeContextMenuUnfavourite : VectorL10n.homeContextMenuFavourite
image:[UIImage systemImageNamed:isFavourite ? @"star.slash" : @"star"]
identifier:nil
handler:^(__kindof UIAction * _Nonnull action) {
MXStrongifyAndReturnIfNil(self);
[self updateRoomWithId:roomId asFavourite:!isFavourite];
}];
BOOL isLowPriority = (currentTag && [kMXRoomTagLowPriority isEqualToString:currentTag.name]);
2022-01-27 16:43:47 +00:00
UIAction *lowPriorityAction = [UIAction actionWithTitle:isLowPriority ? VectorL10n.homeContextMenuNormalPriority : VectorL10n.homeContextMenuLowPriority
image:[UIImage systemImageNamed:isLowPriority ? @"arrow.up" : @"arrow.down"]
identifier:nil
handler:^(__kindof UIAction * _Nonnull action) {
MXStrongifyAndReturnIfNil(self);
[self updateRoomWithId:roomId asLowPriority:!isLowPriority];
}];
2022-01-27 16:43:47 +00:00
UIImage *leaveImage;
if (@available(iOS 14.0, *))
{
leaveImage = [UIImage systemImageNamed:@"rectangle.righthalf.inset.fill.arrow.right"];
}
else
{
leaveImage = [UIImage systemImageNamed:@"rectangle.xmark"];
}
UIAction *leaveAction = [UIAction actionWithTitle:VectorL10n.homeContextMenuLeave
image:leaveImage
identifier:nil
handler:^(__kindof UIAction * _Nonnull action) {
MXStrongifyAndReturnIfNil(self);
[self leaveRoomWithId:roomId];
}];
leaveAction.attributes = UIMenuElementAttributesDestructive;
return [UIMenu menuWithTitle:@"" children:@[
directChatAction,
notificationsAction,
favouriteAction,
lowPriorityAction,
leaveAction
]];
}];
}
2017-05-05 13:30:35 +00:00
#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];
}
// MARK: - Context Menu Actions
- (void)updateRoomWithId:(NSString *)roomId asDirect:(BOOL)direct
{
editedRoomId = roomId;
[self makeDirectEditedRoom:direct];
editedRoomId = nil;
}
- (void)updateRoomWithId:(NSString *)roomId asMuted:(BOOL)muted
{
editedRoomId = roomId;
if ([BuildSettings showNotificationsV2])
{
[self changeEditedRoomNotificationSettings];
}
else
{
[self muteEditedRoomNotifications:muted];
}
editedRoomId = nil;
}
- (void)updateRoomWithId:(NSString *)roomId asFavourite:(BOOL)favourite
{
editedRoomId = roomId;
[self updateEditedRoomTag:favourite ? kMXRoomTagFavourite : nil];
editedRoomId = nil;
}
- (void)updateRoomWithId:(NSString *)roomId asLowPriority:(BOOL)lowPriority
{
editedRoomId = roomId;
[self updateEditedRoomTag:lowPriority ? kMXRoomTagLowPriority : nil];
editedRoomId = nil;
}
- (void)leaveRoomWithId:(NSString *)roomId
{
editedRoomId = roomId;
[self leaveEditedRoom];
editedRoomId = nil;
}
2020-06-26 12:11:58 +00:00
#pragma mark - SecureBackupSetupCoordinatorBridgePresenterDelegate
2019-01-31 18:52:32 +00:00
2020-06-26 12:01:33 +00:00
- (void)secureBackupSetupCoordinatorBridgePresenterDelegateDidComplete:(SecureBackupSetupCoordinatorBridgePresenter *)coordinatorBridgePresenter
2019-01-31 18:52:32 +00:00
{
2020-06-26 11:07:53 +00:00
[self.secureBackupSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
self.secureBackupSetupCoordinatorBridgePresenter = nil;
2019-01-31 18:52:32 +00:00
}
2020-06-26 12:01:33 +00:00
- (void)secureBackupSetupCoordinatorBridgePresenterDelegateDidCancel:(SecureBackupSetupCoordinatorBridgePresenter *)coordinatorBridgePresenter
2019-01-31 18:52:32 +00:00
{
2020-06-26 11:07:53 +00:00
[self.secureBackupSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
self.secureBackupSetupCoordinatorBridgePresenter = nil;
2019-01-31 18:52:32 +00:00
}
2020-06-30 19:46:10 +00:00
#pragma mark - Cross-signing setup
2020-06-30 19:46:10 +00:00
- (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:RecentsDataSourceModeHome];
[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 ?: @"";
2020-11-30 17:22:59 +00:00
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 [UIImage imageNamed:@"home_empty_screen_artwork_dark"];
}
else
{
return [UIImage imageNamed:@"home_empty_screen_artwork"];
}
}
- (BOOL)shouldShowEmptyView
{
// Do not present empty screen while searching
if (recentsDataSource.searchPatternsList.count)
{
return NO;
}
// Check if some banners should be displayed
if (recentsDataSource.secureBackupBannerSection != -1 || recentsDataSource.crossSigningBannerSection != -1)
{
return NO;
}
// Otherwise check the number of items to display
2021-10-04 14:29:04 +00:00
return recentsDataSource.totalVisibleItemCount == 0;
}
#pragma mark - SpaceMembersCoordinatorBridgePresenterDelegate
- (void)spaceMembersCoordinatorBridgePresenterDelegateDidComplete:(SpaceMembersCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
self.spaceMembersCoordinatorBridgePresenter = nil;
}];
}
@end