element-ios/Riot/Modules/Common/Recents/RecentsViewController.m
2021-10-05 16:24:58 +02:00

2310 lines
89 KiB
Objective-C

/*
Copyright 2015 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector 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 "RecentsViewController.h"
#import "RecentsDataSource.h"
#import "RecentTableViewCell.h"
#import "UnifiedSearchViewController.h"
#import "MXRoom+Riot.h"
#import <MatrixKit/MatrixKit.h>
#import "RoomViewController.h"
#import "InviteRecentTableViewCell.h"
#import "DirectoryRecentTableViewCell.h"
#import "RoomIdOrAliasTableViewCell.h"
#import "Riot-Swift.h"
NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewControllerDataReadyNotification";
@interface RecentsViewController () <CreateRoomCoordinatorBridgePresenterDelegate, RoomsDirectoryCoordinatorBridgePresenterDelegate, RoomNotificationSettingsCoordinatorBridgePresenterDelegate>
{
// Tell whether a recents refresh is pending (suspended during editing mode).
BOOL isRefreshPending;
// Recents drag and drop management
UILongPressGestureRecognizer *longPressGestureRecognizer;
UIImageView *cellSnapshot;
NSIndexPath* movingCellPath;
MXRoom* movingRoom;
NSIndexPath* lastPotentialCellPath;
// Observe UIApplicationDidEnterBackgroundNotification to cancel editing mode when app leaves the foreground state.
id UIApplicationDidEnterBackgroundNotificationObserver;
// Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar.
id kAppDelegateDidTapStatusBarNotificationObserver;
// Observe kMXNotificationCenterDidUpdateRules to update missed messages counts.
id kMXNotificationCenterDidUpdateRulesObserver;
MXHTTPOperation *currentRequest;
// The fake search bar displayed at the top of the recents table. We switch on the actual search bar (self.recentsSearchBar)
// when the user selects it.
UISearchBar *tableSearchBar;
// Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change.
id kThemeServiceDidChangeThemeNotificationObserver;
}
@property (nonatomic, strong) CreateRoomCoordinatorBridgePresenter *createRoomCoordinatorBridgePresenter;
@property (nonatomic, strong) RoomsDirectoryCoordinatorBridgePresenter *roomsDirectoryCoordinatorBridgePresenter;
@property (nonatomic, strong) ExploreRoomCoordinatorBridgePresenter *exploreRoomsCoordinatorBridgePresenter;
@property (nonatomic, strong) SpaceFeatureUnavailablePresenter *spaceFeatureUnavailablePresenter;
@property (nonatomic, strong) CustomSizedPresentationController *customSizedPresentationController;
@property (nonatomic, strong) RoomNotificationSettingsCoordinatorBridgePresenter *roomNotificationSettingsCoordinatorBridgePresenter;
@end
@implementation RecentsViewController
#pragma mark - Class methods
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([RecentsViewController class])
bundle:[NSBundle bundleForClass:[RecentsViewController class]]];
}
+ (instancetype)recentListViewController
{
return [[[self class] alloc] initWithNibName:NSStringFromClass([RecentsViewController class])
bundle:[NSBundle bundleForClass:[RecentsViewController class]]];
}
#pragma mark -
- (void)finalizeInit
{
[super finalizeInit];
// Setup `MXKViewControllerHandling` properties
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
// Set default screen name
_screenName = @"RecentsScreen";
// Enable the search bar in the recents table, and remove the search option from the navigation bar.
_enableSearchBar = YES;
self.enableBarButtonSearch = NO;
_enableDragging = NO;
_enableStickyHeaders = NO;
_stickyHeaderHeight = 30.0;
// Create the fake search bar
tableSearchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 600, 44)];
tableSearchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
tableSearchBar.showsCancelButton = NO;
tableSearchBar.placeholder = [VectorL10n searchDefaultPlaceholder];
tableSearchBar.delegate = self;
displayedSectionHeaders = [NSMutableArray array];
// Set itself as delegate by default.
self.delegate = self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.recentsTableView.accessibilityIdentifier = @"RecentsVCTableView";
// Register here the customized cell view class used to render recents
[self.recentsTableView registerNib:RecentTableViewCell.nib forCellReuseIdentifier:RecentTableViewCell.defaultReuseIdentifier];
[self.recentsTableView registerNib:InviteRecentTableViewCell.nib forCellReuseIdentifier:InviteRecentTableViewCell.defaultReuseIdentifier];
// Register key backup banner cells
[self.recentsTableView registerNib:SecureBackupBannerCell.nib forCellReuseIdentifier:SecureBackupBannerCell.defaultReuseIdentifier];
// Register key verification banner cells
[self.recentsTableView registerNib:CrossSigningSetupBannerCell.nib forCellReuseIdentifier:CrossSigningSetupBannerCell.defaultReuseIdentifier];
// Hide line separators of empty cells
self.recentsTableView.tableFooterView = [[UIView alloc] init];
// Apply dragging settings
self.enableDragging = _enableDragging;
// Observe UIApplicationDidEnterBackgroundNotification to refresh bubbles when app leaves the foreground state.
UIApplicationDidEnterBackgroundNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
// Leave potential editing mode
[self cancelEditionMode:isRefreshPending];
}];
self.recentsSearchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
self.recentsSearchBar.placeholder = [VectorL10n searchDefaultPlaceholder];
// Observe user interface theme change.
kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
[self userInterfaceThemeDidChange];
}];
[self userInterfaceThemeDidChange];
}
- (void)userInterfaceThemeDidChange
{
[ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar];
self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor;
// Use the primary bg color for the recents table view in plain style.
self.recentsTableView.backgroundColor = ThemeService.shared.theme.backgroundColor;
self.recentsTableView.separatorColor = ThemeService.shared.theme.lineBreakColor;
topview.backgroundColor = ThemeService.shared.theme.headerBackgroundColor;
self.view.backgroundColor = ThemeService.shared.theme.backgroundColor;
[ThemeService.shared.theme applyStyleOnSearchBar:tableSearchBar];
[ThemeService.shared.theme applyStyleOnSearchBar:self.recentsSearchBar];
if (self.recentsTableView.dataSource)
{
// Force table refresh
[self cancelEditionMode:YES];
}
[self.emptyView updateWithTheme:ThemeService.shared.theme];
[self setNeedsStatusBarAppearanceUpdate];
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
return ThemeService.shared.theme.statusBarStyle;
}
- (void)destroy
{
[super destroy];
longPressGestureRecognizer = nil;
if (currentRequest)
{
[currentRequest cancel];
currentRequest = nil;
}
if (currentAlert)
{
[currentAlert dismissViewControllerAnimated:NO completion:nil];
currentAlert = nil;
}
if (UIApplicationDidEnterBackgroundNotificationObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:UIApplicationDidEnterBackgroundNotificationObserver];
UIApplicationDidEnterBackgroundNotificationObserver = nil;
}
if (kThemeServiceDidChangeThemeNotificationObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver];
kThemeServiceDidChangeThemeNotificationObserver = nil;
}
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
self.recentsTableView.editing = editing;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:_screenName];
// Reset back user interactions
self.userInteractionEnabled = YES;
// Deselect the current selected row, it will be restored on viewDidAppear (if any)
NSIndexPath *indexPath = [self.recentsTableView indexPathForSelectedRow];
if (indexPath)
{
[self.recentsTableView deselectRowAtIndexPath:indexPath animated:NO];
}
// Observe kAppDelegateDidTapStatusBarNotificationObserver.
kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
[self scrollToTop:YES];
}];
// Observe kMXNotificationCenterDidUpdateRules to refresh missed messages counts
kMXNotificationCenterDidUpdateRulesObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXNotificationCenterDidUpdateRules object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self refreshRecentsTable];
}];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// Leave potential editing mode
[self cancelEditionMode:NO];
if (kAppDelegateDidTapStatusBarNotificationObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:kAppDelegateDidTapStatusBarNotificationObserver];
kAppDelegateDidTapStatusBarNotificationObserver = nil;
}
if (kMXNotificationCenterDidUpdateRulesObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:kMXNotificationCenterDidUpdateRulesObserver];
kMXNotificationCenterDidUpdateRulesObserver = nil;
}
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Release the current selected item (if any) except if the second view controller is still visible.
if (self.splitViewController.isCollapsed)
{
// Release the current selected room (if any).
[[AppDelegate theDelegate].masterTabBarController releaseSelectedItem];
}
else
{
// In case of split view controller where the primary and secondary view controllers are displayed side-by-side onscreen,
// the selected room (if any) is highlighted.
[self refreshCurrentSelectedCell:YES];
}
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
dispatch_async(dispatch_get_main_queue(), ^{
[self refreshStickyHeadersContainersHeight];
});
}
#pragma mark - Override MXKRecentListViewController
- (void)refreshRecentsTable
{
// Refresh the tabBar icon badges
[[AppDelegate theDelegate].masterTabBarController refreshTabBarBadges];
// do not refresh if there is a pending recent drag and drop
if (movingCellPath)
{
return;
}
isRefreshPending = NO;
if (editedRoomId)
{
// Check whether the user didn't leave the room
MXRoom *room = [self.mainSession roomWithRoomId:editedRoomId];
if (room)
{
isRefreshPending = YES;
return;
}
else
{
// Cancel the editing mode, a new refresh will be triggered.
[self cancelEditionMode:YES];
return;
}
}
// Force reset existing sticky headers if any
[self resetStickyHeaders];
[self.recentsTableView reloadData];
// Check conditions to display the fake search bar into the table header
if (_enableSearchBar && self.recentsSearchBar.isHidden && self.recentsTableView.tableHeaderView == nil)
{
// Add the search bar by hiding it by default.
self.recentsTableView.tableHeaderView = tableSearchBar;
self.recentsTableView.contentOffset = CGPointMake(0, self.recentsTableView.contentOffset.y + tableSearchBar.frame.size.height);
}
if (_shouldScrollToTopOnRefresh)
{
[self scrollToTop:NO];
_shouldScrollToTopOnRefresh = NO;
}
[self prepareStickyHeaders];
// In case of split view controller where the primary and secondary view controllers are displayed side-by-side on screen,
// the selected room (if any) is updated.
if (!self.splitViewController.isCollapsed)
{
[self refreshCurrentSelectedCell:NO];
}
}
- (void)hideSearchBar:(BOOL)hidden
{
[super hideSearchBar:hidden];
if (!hidden)
{
// Remove the fake table header view if any
self.recentsTableView.tableHeaderView = nil;
self.recentsTableView.contentInset = UIEdgeInsetsZero;
}
}
#pragma mark -
- (void)refreshCurrentSelectedCell:(BOOL)forceVisible
{
// Update here the index of the current selected cell (if any) - Useful in landscape mode with split view controller.
NSIndexPath *currentSelectedCellIndexPath = nil;
MasterTabBarController *masterTabBarController = [AppDelegate theDelegate].masterTabBarController;
if (masterTabBarController.selectedRoomId)
{
// Look for the rank of this selected room in displayed recents
currentSelectedCellIndexPath = [self.dataSource cellIndexPathWithRoomId:masterTabBarController.selectedRoomId andMatrixSession:masterTabBarController.selectedRoomSession];
}
if (currentSelectedCellIndexPath)
{
// Select the right row
[self.recentsTableView selectRowAtIndexPath:currentSelectedCellIndexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
if (forceVisible)
{
// Scroll table view to make the selected row appear at second position
NSInteger topCellIndexPathRow = currentSelectedCellIndexPath.row ? currentSelectedCellIndexPath.row - 1: currentSelectedCellIndexPath.row;
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:topCellIndexPathRow inSection:currentSelectedCellIndexPath.section];
if ([self.recentsTableView vc_hasIndexPath:indexPath])
{
[self.recentsTableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:NO];
}
}
}
else
{
NSIndexPath *indexPath = [self.recentsTableView indexPathForSelectedRow];
if (indexPath)
{
[self.recentsTableView deselectRowAtIndexPath:indexPath animated:NO];
}
}
}
- (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];
}
}
}
- (void)joinRoom:(MXRoom*)room completion:(void(^)(BOOL succeed))completion
{
[room join:^{
// `recentsTableView` will be reloaded `roomChangeMembershipStateDataSourceDidChangeRoomMembershipState` function
if (completion)
{
completion(YES);
}
} failure:^(NSError * _Nonnull error) {
MXLogDebug(@"[RecentsViewController] Failed to join an invited room (%@)", room.roomId);
[self presentRoomJoinFailedAlertForError:error completion:^{
if (completion)
{
completion(NO);
}
}];
}];
}
- (void)leaveRoom:(MXRoom*)room completion:(void(^)(BOOL succeed))completion
{
// Decline the invitation
[room leave:^{
// `recentsTableView` will be reloaded `roomChangeMembershipStateDataSourceDidChangeRoomMembershipState` function
if (completion)
{
completion(YES);
}
} failure:^(NSError * _Nonnull error) {
MXLogDebug(@"[RecentsViewController] Failed to reject an invited room (%@)", room.roomId);
[[AppDelegate theDelegate] showErrorAsAlert:error];
if (completion)
{
completion(NO);
}
}];
}
- (void)presentRoomJoinFailedAlertForError:(NSError*)error completion:(void(^)(void))completion
{
MXWeakify(self);
NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
if ([msg isEqualToString:@"No known servers"])
{
// minging kludge until https://matrix.org/jira/browse/SYN-678 is fixed
// 'Error when trying to join an empty room should be more explicit'
msg = [MatrixKitL10n roomErrorJoinFailedEmptyRoom];
}
[self->currentAlert dismissViewControllerAnimated:NO completion:nil];
self->currentAlert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n roomErrorJoinFailedTitle] message:msg preferredStyle:UIAlertControllerStyleAlert];
[self->currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
MXStrongifyAndReturnIfNil(self);
self->currentAlert = nil;
if (completion)
{
completion();
}
}]];
[self presentViewController:self->currentAlert animated:YES completion:nil];
}
#pragma mark - Sticky Headers
- (void)setEnableStickyHeaders:(BOOL)enableStickyHeaders
{
_enableStickyHeaders = enableStickyHeaders;
// Refresh the table display if it is already rendered.
if (self.recentsTableView.contentSize.height)
{
[self refreshRecentsTable];
}
}
- (void)setStickyHeaderHeight:(CGFloat)stickyHeaderHeight
{
if (_stickyHeaderHeight != stickyHeaderHeight)
{
_stickyHeaderHeight = stickyHeaderHeight;
// Force a sticky headers refresh
self.enableStickyHeaders = _enableStickyHeaders;
}
}
- (UIView *)tableView:(UITableView *)tableView viewForStickyHeaderInSection:(NSInteger)section
{
// Return the section header by default.
return [self tableView:tableView viewForHeaderInSection:section];
}
- (void)resetStickyHeaders
{
// Release sticky header
_stickyHeadersTopContainerHeightConstraint.constant = 0;
_stickyHeadersBottomContainerHeightConstraint.constant = 0;
for (UIView *view in _stickyHeadersTopContainer.subviews)
{
[view removeFromSuperview];
}
for (UIView *view in _stickyHeadersBottomContainer.subviews)
{
[view removeFromSuperview];
}
[displayedSectionHeaders removeAllObjects];
self.recentsTableView.contentInset = UIEdgeInsetsZero;
}
- (void)prepareStickyHeaders
{
// We suppose here [resetStickyHeaders] has been already called if need.
NSInteger sectionsCount = self.recentsTableView.numberOfSections;
if (self.enableStickyHeaders && sectionsCount)
{
NSUInteger topContainerOffset = 0;
NSUInteger bottomContainerOffset = 0;
CGRect frame;
UIView *stickyHeader = [self viewForStickyHeaderInSection:0 withSwipeGestureRecognizerInDirection:UISwipeGestureRecognizerDirectionDown];
frame = stickyHeader.frame;
frame.origin.y = topContainerOffset;
stickyHeader.frame = frame;
[self.stickyHeadersTopContainer addSubview:stickyHeader];
topContainerOffset = stickyHeader.frame.size.height;
for (NSUInteger index = 1; index < sectionsCount; index++)
{
stickyHeader = [self viewForStickyHeaderInSection:index withSwipeGestureRecognizerInDirection:UISwipeGestureRecognizerDirectionDown];
frame = stickyHeader.frame;
frame.origin.y = topContainerOffset;
stickyHeader.frame = frame;
[self.stickyHeadersTopContainer addSubview:stickyHeader];
topContainerOffset += frame.size.height;
stickyHeader = [self viewForStickyHeaderInSection:index withSwipeGestureRecognizerInDirection:UISwipeGestureRecognizerDirectionUp];
frame = stickyHeader.frame;
frame.origin.y = bottomContainerOffset;
stickyHeader.frame = frame;
[self.stickyHeadersBottomContainer addSubview:stickyHeader];
bottomContainerOffset += frame.size.height;
}
[self refreshStickyHeadersContainersHeight];
}
}
- (UIView *)viewForStickyHeaderInSection:(NSInteger)section withSwipeGestureRecognizerInDirection:(UISwipeGestureRecognizerDirection)swipeDirection
{
UIView *stickyHeader = [self tableView:self.recentsTableView viewForStickyHeaderInSection:section];
stickyHeader.tag = section;
stickyHeader.autoresizingMask = UIViewAutoresizingFlexibleWidth;
// Remove existing gesture recognizers
while (stickyHeader.gestureRecognizers.count)
{
UIGestureRecognizer *gestureRecognizer = stickyHeader.gestureRecognizers.lastObject;
[stickyHeader removeGestureRecognizer:gestureRecognizer];
}
// Handle tap gesture, the section is moved up on the tap.
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapOnSectionHeader:)];
[tap setNumberOfTouchesRequired:1];
[tap setNumberOfTapsRequired:1];
[stickyHeader addGestureRecognizer:tap];
// Handle vertical swipe gesture with the provided direction, by default the section will be moved up on this swipe.
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(didSwipeOnSectionHeader:)];
[swipe setNumberOfTouchesRequired:1];
[swipe setDirection:swipeDirection];
[stickyHeader addGestureRecognizer:swipe];
return stickyHeader;
}
- (void)didTapOnSectionHeader:(UIGestureRecognizer*)gestureRecognizer
{
UIView *view = gestureRecognizer.view;
NSInteger section = view.tag;
// Scroll to the top of this section
if ([self.recentsTableView numberOfRowsInSection:section] > 0)
{
[self.recentsTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
}
- (void)didSwipeOnSectionHeader:(UISwipeGestureRecognizer*)gestureRecognizer
{
UIView *view = gestureRecognizer.view;
NSInteger section = view.tag;
if ([self.recentsTableView numberOfRowsInSection:section] > 0)
{
// Check whether the first cell of this section is already visible.
UITableViewCell *firstSectionCell = [self.recentsTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
if (firstSectionCell)
{
// Scroll to the top of the previous section (if any)
if (section && [self.recentsTableView numberOfRowsInSection:(section - 1)] > 0)
{
[self.recentsTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:(section - 1)] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
}
else
{
// Scroll to the top of this section
[self.recentsTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
}
}
- (void)refreshStickyHeadersContainersHeight
{
if (_enableStickyHeaders)
{
NSUInteger lowestSectionInBottomStickyHeader = NSNotFound;
CGFloat containerHeight;
// Retrieve the first header actually visible in the recents table view.
// Caution: In some cases like the screen rotation, some displayed section headers are temporarily not visible.
UIView *firstDisplayedSectionHeader;
for (UIView *header in displayedSectionHeaders)
{
if (header.frame.origin.y + header.frame.size.height > self.recentsTableView.contentOffset.y)
{
firstDisplayedSectionHeader = header;
break;
}
}
if (firstDisplayedSectionHeader)
{
// Initialize the top container height by considering the headers which are before the first visible section header.
containerHeight = 0;
for (UIView *header in _stickyHeadersTopContainer.subviews)
{
if (header.tag < firstDisplayedSectionHeader.tag)
{
containerHeight += self.stickyHeaderHeight;
}
}
// Check whether the first visible section header is partially hidden.
if (firstDisplayedSectionHeader.frame.origin.y < self.recentsTableView.contentOffset.y)
{
// Compute the height of the hidden part.
CGFloat delta = self.recentsTableView.contentOffset.y - firstDisplayedSectionHeader.frame.origin.y;
if (delta < self.stickyHeaderHeight)
{
containerHeight += delta;
}
else
{
containerHeight += self.stickyHeaderHeight;
}
}
if (containerHeight)
{
self.stickyHeadersTopContainerHeightConstraint.constant = containerHeight;
self.recentsTableView.contentInset = UIEdgeInsetsMake(-self.stickyHeaderHeight, 0, 0, 0);
}
else
{
self.stickyHeadersTopContainerHeightConstraint.constant = 0;
self.recentsTableView.contentInset = UIEdgeInsetsZero;
}
// Look for the lowest section index visible in the bottom sticky headers.
CGFloat maxVisiblePosY = self.recentsTableView.contentOffset.y + self.recentsTableView.frame.size.height - self.recentsTableView.adjustedContentInset.bottom;
UIView *lastDisplayedSectionHeader = displayedSectionHeaders.lastObject;
for (UIView *header in _stickyHeadersBottomContainer.subviews)
{
if (header.tag > lastDisplayedSectionHeader.tag)
{
maxVisiblePosY -= self.stickyHeaderHeight;
}
}
for (NSInteger index = displayedSectionHeaders.count; index > 0;)
{
lastDisplayedSectionHeader = displayedSectionHeaders[--index];
if (lastDisplayedSectionHeader.frame.origin.y + self.stickyHeaderHeight > maxVisiblePosY)
{
maxVisiblePosY -= self.stickyHeaderHeight;
}
else
{
lowestSectionInBottomStickyHeader = lastDisplayedSectionHeader.tag + 1;
break;
}
}
}
else
{
// Handle here the case where no section header is currently displayed in the table.
// No more than one section is then displayed, we retrieve this section by checking the first visible cell.
NSIndexPath *firstCellIndexPath = [self.recentsTableView indexPathForRowAtPoint:CGPointMake(0, self.recentsTableView.contentOffset.y)];
if (firstCellIndexPath)
{
NSInteger section = firstCellIndexPath.section;
// Refresh top container of the sticky headers
CGFloat containerHeight = 0;
for (UIView *header in _stickyHeadersTopContainer.subviews)
{
if (header.tag <= section)
{
containerHeight += header.frame.size.height;
}
}
self.stickyHeadersTopContainerHeightConstraint.constant = containerHeight;
if (containerHeight)
{
self.recentsTableView.contentInset = UIEdgeInsetsMake(-self.stickyHeaderHeight, 0, 0, 0);
}
else
{
self.recentsTableView.contentInset = UIEdgeInsetsZero;
}
// Set the lowest section index visible in the bottom sticky headers.
lowestSectionInBottomStickyHeader = section + 1;
}
}
// Update here the height of the bottom container of the sticky headers thanks to lowestSectionInBottomStickyHeader.
containerHeight = 0;
CGRect bounds = _stickyHeadersBottomContainer.frame;
bounds.origin.y = 0;
for (UIView *header in _stickyHeadersBottomContainer.subviews)
{
if (header.tag > lowestSectionInBottomStickyHeader)
{
containerHeight += self.stickyHeaderHeight;
}
else if (header.tag == lowestSectionInBottomStickyHeader)
{
containerHeight += self.stickyHeaderHeight;
bounds.origin.y = header.frame.origin.y;
}
}
if (self.stickyHeadersBottomContainerHeightConstraint.constant != containerHeight)
{
self.stickyHeadersBottomContainerHeightConstraint.constant = containerHeight;
self.stickyHeadersBottomContainer.bounds = bounds;
}
}
}
#pragma mark - Internal methods
- (void)showPublicRoomsDirectory
{
// Here the recents view controller is displayed inside a unified search view controller.
// Sanity check
if (self.parentViewController && [self.parentViewController isKindOfClass:UnifiedSearchViewController.class])
{
// Show the directory screen
[((UnifiedSearchViewController*)self.parentViewController) showPublicRoomsDirectory];
}
}
- (void)dispayRoomWithRoomId:(NSString*)roomId inMatrixSession:(MXSession*)matrixSession
{
// Avoid multiple openings of rooms
self.userInteractionEnabled = NO;
[[AppDelegate theDelegate] showRoom:roomId andEventId:nil withMatrixSession:matrixSession restoreInitialDisplay:NO completion:^{
self.userInteractionEnabled = YES;
}];
}
// Disable UI interactions in this screen while we are going to open another screen.
// Interactions on reset on viewWillAppear.
- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled
{
self.view.userInteractionEnabled = userInteractionEnabled;
}
- (RecentsDataSource*)recentsDataSource
{
RecentsDataSource* recentsDataSource = nil;
if ([self.dataSource isKindOfClass:[RecentsDataSource class]])
{
recentsDataSource = (RecentsDataSource*)self.dataSource;
}
return recentsDataSource;
}
- (void)showSpaceInviteNotAvailable
{
if (!self.spaceFeatureUnavailablePresenter)
{
self.spaceFeatureUnavailablePresenter = [SpaceFeatureUnavailablePresenter new];
}
[self.spaceFeatureUnavailablePresenter presentUnavailableFeatureFrom:self animated:YES];
}
#pragma mark - MXKDataSourceDelegate
- (Class<MXKCellRendering>)cellViewClassForCellData:(MXKCellData*)cellData
{
id<MXKRecentCellDataStoring> cellDataStoring = (id<MXKRecentCellDataStoring> )cellData;
if (cellDataStoring.roomSummary.room.summary.membership != MXMembershipInvite)
{
return RecentTableViewCell.class;
}
else
{
return InviteRecentTableViewCell.class;
}
}
- (NSString *)cellReuseIdentifierForCellData:(MXKCellData*)cellData
{
Class class = [self cellViewClassForCellData:cellData];
if ([class respondsToSelector:@selector(defaultReuseIdentifier)])
{
return [class defaultReuseIdentifier];
}
return nil;
}
- (void)dataSource:(MXKDataSource *)dataSource didRecognizeAction:(NSString *)actionIdentifier inCell:(id<MXKCellRendering>)cell userInfo:(NSDictionary *)userInfo
{
// Handle here user actions on recents for Riot app
if ([actionIdentifier isEqualToString:kInviteRecentTableViewCellPreviewButtonPressed])
{
// Retrieve the invited room
MXRoom *invitedRoom = userInfo[kInviteRecentTableViewCellRoomKey];
if (invitedRoom.summary.roomType == MXRoomTypeSpace)
{
// Indicates that spaces are not supported
[self showSpaceInviteNotAvailable];
return;
}
// Display the room preview
[self dispayRoomWithRoomId:invitedRoom.roomId inMatrixSession:invitedRoom.mxSession];
}
else if ([actionIdentifier isEqualToString:kInviteRecentTableViewCellAcceptButtonPressed])
{
// Retrieve the invited room
MXRoom *invitedRoom = userInfo[kInviteRecentTableViewCellRoomKey];
if (invitedRoom.summary.roomType == MXRoomTypeSpace)
{
// Indicates that spaces are not supported
[self showSpaceInviteNotAvailable];
return;
}
// Accept invitation
[self joinRoom:invitedRoom completion:nil];
}
else if ([actionIdentifier isEqualToString:kInviteRecentTableViewCellDeclineButtonPressed])
{
// Retrieve the invited room
MXRoom *invitedRoom = userInfo[kInviteRecentTableViewCellRoomKey];
[self cancelEditionMode:isRefreshPending];
// Decline the invitation
[self leaveRoom:invitedRoom completion:nil];
}
else
{
// Keep default implementation for other actions if any
if ([super respondsToSelector:@selector(cell:didRecognizeAction:userInfo:)])
{
[super dataSource:dataSource didRecognizeAction:actionIdentifier inCell:cell userInfo:userInfo];
}
}
}
- (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes
{
[super dataSource:dataSource didCellChange:changes];
[self showEmptyViewIfNeeded];
if (dataSource.state == MXKDataSourceStateReady)
{
[[NSNotificationCenter defaultCenter] postNotificationName:RecentsViewControllerDataReadyNotification
object:self];
}
}
#pragma mark - Swipe actions
- (void)tableView:(UITableView*)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
[self cancelEditionMode:isRefreshPending];
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewCellEditingStyleNone;
}
- (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
{
MXRoom *room = [self.dataSource getRoomAtIndexPath:indexPath];
if (!room)
{
return nil;
}
// Display no action for the invited room
if (room.summary.membership == MXMembershipInvite)
{
return nil;
}
// Store the identifier of the room related to the edited cell.
editedRoomId = room.roomId;
UIColor *selectedColor = ThemeService.shared.theme.tintColor;
UIColor *unselectedColor = ThemeService.shared.theme.tabBarUnselectedItemTintColor;
UIColor *actionBackgroundColor = ThemeService.shared.theme.baseColor;
NSString* title = @" ";
// Direct chat toggle
BOOL isDirect = room.isDirect;
UIContextualAction *directChatAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive
title:title
handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
[self makeDirectEditedRoom:!isDirect];
completionHandler(YES);
}];
directChatAction.backgroundColor = actionBackgroundColor;
UIImage *directChatImage = [UIImage imageNamed:@"room_action_direct_chat"];
directChatImage = [directChatImage vc_tintedImageUsingColor:isDirect ? selectedColor : unselectedColor];
directChatAction.image = [directChatImage vc_notRenderedImage];
// Notification toggle
BOOL isMuted = room.isMute || room.isMentionsOnly;
UIContextualAction *muteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive
title:title
handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
if ([BuildSettings showNotificationsV2])
{
[self changeEditedRoomNotificationSettings];
}
else
{
[self muteEditedRoomNotifications:!isMuted];
}
completionHandler(YES);
}];
muteAction.backgroundColor = actionBackgroundColor;
UIImage *notificationImage;
if([BuildSettings showNotificationsV2])
{
notificationImage = isMuted ? [UIImage imageNamed:@"room_action_notification_muted"] : [UIImage imageNamed:@"room_action_notification"];
}
else
{
notificationImage = [UIImage imageNamed:@"room_action_notification"];
}
notificationImage = [notificationImage vc_tintedImageUsingColor:isMuted ? unselectedColor : selectedColor];
muteAction.image = [notificationImage vc_notRenderedImage];
// Favorites management
MXRoomTag* currentTag = nil;
// Get the room tag (use only the first one).
if (room.accountData.tags)
{
NSArray<MXRoomTag*>* tags = room.accountData.tags.allValues;
if (tags.count)
{
currentTag = tags[0];
}
}
BOOL isFavourite = (currentTag && [kMXRoomTagFavourite isEqualToString:currentTag.name]);
UIContextualAction *favouriteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive
title:title
handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
NSString *favouriteTag = isFavourite ? nil : kMXRoomTagFavourite;
[self updateEditedRoomTag:favouriteTag];
completionHandler(YES);
}];
favouriteAction.backgroundColor = actionBackgroundColor;
UIImage *favouriteImage = [UIImage imageNamed:@"room_action_favourite"];
favouriteImage = [favouriteImage vc_tintedImageUsingColor:isFavourite ? selectedColor : unselectedColor];
favouriteAction.image = [favouriteImage vc_notRenderedImage];
// Priority toggle
BOOL isInLowPriority = (currentTag && [kMXRoomTagLowPriority isEqualToString:currentTag.name]);
UIContextualAction *priorityAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive
title:title
handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
NSString *priorityTag = isInLowPriority ? nil : kMXRoomTagLowPriority;
[self updateEditedRoomTag:priorityTag];
completionHandler(YES);
}];
priorityAction.backgroundColor = actionBackgroundColor;
UIImage *priorityImage = isInLowPriority ? [UIImage imageNamed:@"room_action_priority_high"] : [UIImage imageNamed:@"room_action_priority_low"];
priorityImage = [priorityImage vc_tintedImageUsingColor:unselectedColor];
priorityAction.image = [priorityImage vc_notRenderedImage];
// Leave action
UIContextualAction *leaveAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive
title:title
handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
[self leaveEditedRoom];
completionHandler(YES);
}];
leaveAction.backgroundColor = actionBackgroundColor;
UIImage *leaveImage = [UIImage imageNamed:@"room_action_leave"];
leaveImage = [leaveImage vc_tintedImageUsingColor:unselectedColor];
leaveAction.image = [leaveImage vc_notRenderedImage];
// Create swipe action configuration
NSArray<UIContextualAction*> *actions = @[
leaveAction,
priorityAction,
favouriteAction,
muteAction,
directChatAction
];
UISwipeActionsConfiguration *swipeActionConfiguration = [UISwipeActionsConfiguration configurationWithActions:actions];
swipeActionConfiguration.performsFirstActionWithFullSwipe = NO;
return swipeActionConfiguration;
}
- (void)leaveEditedRoom
{
if (editedRoomId)
{
NSString *currentRoomId = editedRoomId;
__weak typeof(self) weakSelf = self;
NSString *title, *message;
if ([self.mainSession roomWithRoomId:currentRoomId].isDirect)
{
title = [VectorL10n roomParticipantsLeavePromptTitleForDm];
message = [VectorL10n roomParticipantsLeavePromptMsgForDm];
}
else
{
title = [VectorL10n roomParticipantsLeavePromptTitle];
message = [VectorL10n roomParticipantsLeavePromptMsg];
}
// confirm leave
currentAlert = [UIAlertController alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
[currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel]
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
}
}]];
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n leave]
style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
// Check whether the user didn't leave the room yet
// TODO: Handle multi-account
MXRoom *room = [self.mainSession roomWithRoomId:currentRoomId];
if (room)
{
[self startActivityIndicator];
// cancel pending uploads/downloads
// they are useless by now
[MXMediaManager cancelDownloadsInCacheFolder:room.roomId];
// TODO GFO cancel pending uploads related to this room
MXLogDebug(@"[RecentsViewController] Leave room (%@)", room.roomId);
[room leave:^{
if (weakSelf)
{
typeof(self) self = weakSelf;
[self stopActivityIndicator];
// Force table refresh
[self cancelEditionMode:YES];
}
} failure:^(NSError *error) {
MXLogDebug(@"[RecentsViewController] Failed to leave room");
if (weakSelf)
{
typeof(self) self = weakSelf;
// Notify the end user
NSString *userId = room.mxSession.myUser.userId;
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification
object:error
userInfo:userId ? @{kMXKErrorUserIdKey: userId} : nil];
[self stopActivityIndicator];
// Leave editing mode
[self cancelEditionMode:isRefreshPending];
}
}];
}
else
{
// Leave editing mode
[self cancelEditionMode:isRefreshPending];
}
}
}]];
[currentAlert mxk_setAccessibilityIdentifier:@"LeaveEditedRoomAlert"];
[self presentViewController:currentAlert animated:YES completion:nil];
}
}
- (void)updateEditedRoomTag:(NSString*)tag
{
if (editedRoomId)
{
// Check whether the user didn't leave the room
MXRoom *room = [self.mainSession roomWithRoomId:editedRoomId];
if (room)
{
[self startActivityIndicator];
[room setRoomTag:tag completion:^{
[self stopActivityIndicator];
// Force table refresh
[self cancelEditionMode:YES];
}];
}
else
{
// Leave editing mode
[self cancelEditionMode:isRefreshPending];
}
}
}
- (void)makeDirectEditedRoom:(BOOL)isDirect
{
if (editedRoomId)
{
__weak typeof(self) weakSelf = self;
// Check whether the user didn't leave the room
// TODO: handle multi-account
MXRoom *room = [self.mainSession roomWithRoomId:editedRoomId];
if (room)
{
[self startActivityIndicator];
[room setIsDirect:isDirect withUserId:nil success:^{
if (weakSelf)
{
typeof(self) self = weakSelf;
[self stopActivityIndicator];
// Leave editing mode
[self cancelEditionMode:isRefreshPending];
}
} failure:^(NSError *error) {
if (weakSelf)
{
typeof(self) self = weakSelf;
[self stopActivityIndicator];
MXLogDebug(@"[RecentsViewController] Failed to update direct tag of the room (%@)", editedRoomId);
// Notify the end user
NSString *userId = self.mainSession.myUser.userId; // TODO: handle multi-account
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification
object:error
userInfo:userId ? @{kMXKErrorUserIdKey: userId} : nil];
// Leave editing mode
[self cancelEditionMode:isRefreshPending];
}
}];
}
else
{
// Leave editing mode
[self cancelEditionMode:isRefreshPending];
}
}
}
- (void)changeEditedRoomNotificationSettings
{
if (editedRoomId)
{
// Check whether the user didn't leave the room
MXRoom *room = [self.mainSession roomWithRoomId:editedRoomId];
if (room)
{
// navigate
self.roomNotificationSettingsCoordinatorBridgePresenter = [[RoomNotificationSettingsCoordinatorBridgePresenter alloc] initWithRoom:room];
self.roomNotificationSettingsCoordinatorBridgePresenter.delegate = self;
[self.roomNotificationSettingsCoordinatorBridgePresenter presentFrom:self animated:YES];
}
[self cancelEditionMode:isRefreshPending];
}
}
- (void)muteEditedRoomNotifications:(BOOL)mute
{
if (editedRoomId)
{
// Check whether the user didn't leave the room
MXRoom *room = [self.mainSession roomWithRoomId:editedRoomId];
if (room)
{
[self startActivityIndicator];
if (mute)
{
[room mentionsOnly:^{
[self stopActivityIndicator];
// Leave editing mode
[self cancelEditionMode:self->isRefreshPending];
}];
}
else
{
[room allMessages:^{
[self stopActivityIndicator];
// Leave editing mode
[self cancelEditionMode:self->isRefreshPending];
}];
}
}
else
{
// Leave editing mode
[self cancelEditionMode:isRefreshPending];
}
}
}
#pragma mark - UITableView delegate
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
{
cell.backgroundColor = ThemeService.shared.theme.backgroundColor;
// Update the selected background view
if (ThemeService.shared.theme.selectedBackgroundColor)
{
cell.selectedBackgroundView = [[UIView alloc] init];
cell.selectedBackgroundView.backgroundColor = ThemeService.shared.theme.selectedBackgroundColor;
}
else
{
if (tableView.style == UITableViewStylePlain)
{
cell.selectedBackgroundView = nil;
}
else
{
cell.selectedBackgroundView.backgroundColor = nil;
}
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 30.0f;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *sectionHeader = [super tableView:tableView viewForHeaderInSection:section];
sectionHeader.tag = section;
while (sectionHeader.gestureRecognizers.count)
{
UIGestureRecognizer *gestureRecognizer = sectionHeader.gestureRecognizers.lastObject;
[sectionHeader removeGestureRecognizer:gestureRecognizer];
}
// Handle tap gesture
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapOnSectionHeader:)];
[tap setNumberOfTouchesRequired:1];
[tap setNumberOfTapsRequired:1];
[sectionHeader addGestureRecognizer:tap];
return sectionHeader;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = [self.recentsTableView cellForRowAtIndexPath:indexPath];
if ([cell isKindOfClass:[InviteRecentTableViewCell class]])
{
id<MXKRecentCellDataStoring> cellData = [self.dataSource cellDataAtIndexPath:indexPath];
// Retrieve the invited room
MXRoom* invitedRoom = cellData.roomSummary.room;
if (invitedRoom.summary.roomType == MXRoomTypeSpace)
{
// Indicates that spaces are not supported
[self showSpaceInviteNotAvailable];
}
// Check if can show preview for the invited room
else if ([self canShowRoomPreviewFor:invitedRoom])
{
// Display the room preview
[self dispayRoomWithRoomId:invitedRoom.roomId inMatrixSession:invitedRoom.mxSession];
}
else
{
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
}
else if ([cell isKindOfClass:[DirectoryRecentTableViewCell class]])
{
[self showPublicRoomsDirectory];
}
else if ([cell isKindOfClass:[RoomIdOrAliasTableViewCell class]])
{
NSString *roomIdOrAlias = ((RoomIdOrAliasTableViewCell*)cell).titleLabel.text;
if (roomIdOrAlias.length)
{
// Open the room or preview it
NSString *fragment = [NSString stringWithFormat:@"/room/%@", [MXTools encodeURIComponent:roomIdOrAlias]];
[[AppDelegate theDelegate] handleUniversalLinkFragment:fragment];
}
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
else
{
[super tableView:tableView didSelectRowAtIndexPath:indexPath];
}
}
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
{
if (_enableStickyHeaders)
{
view.tag = section;
UIView *firstDisplayedSectionHeader = displayedSectionHeaders.firstObject;
if (!firstDisplayedSectionHeader || section < firstDisplayedSectionHeader.tag)
{
[displayedSectionHeaders insertObject:view atIndex:0];
}
else
{
[displayedSectionHeaders addObject:view];
}
[self refreshStickyHeadersContainersHeight];
}
}
- (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section
{
if (_enableStickyHeaders)
{
UIView *firstDisplayedSectionHeader = displayedSectionHeaders.firstObject;
if (firstDisplayedSectionHeader)
{
if (section == firstDisplayedSectionHeader.tag)
{
[displayedSectionHeaders removeObjectAtIndex:0];
[self refreshStickyHeadersContainersHeight];
}
else
{
// This section header is the last displayed one.
// Add a sanity check in case of the header has been already removed.
UIView *lastDisplayedSectionHeader = displayedSectionHeaders.lastObject;
if (section == lastDisplayedSectionHeader.tag)
{
[displayedSectionHeaders removeLastObject];
[self refreshStickyHeadersContainersHeight];
}
}
}
}
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
dispatch_async(dispatch_get_main_queue(), ^{
[self refreshStickyHeadersContainersHeight];
});
[super scrollViewDidScroll:scrollView];
if (scrollView == self.recentsTableView)
{
if (!self.recentsSearchBar.isHidden)
{
if (!self.recentsSearchBar.text.length && (scrollView.contentOffset.y + scrollView.adjustedContentInset.top > self.recentsSearchBar.frame.size.height))
{
// Hide the search bar
[self hideSearchBar:YES];
// Refresh display
[self refreshRecentsTable];
}
}
}
}
#pragma mark - Recents drag & drop management
- (void)setEnableDragging:(BOOL)enableDragging
{
_enableDragging = enableDragging;
if (_enableDragging && !longPressGestureRecognizer && self.recentsTableView)
{
longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onRecentsLongPress:)];
[self.recentsTableView addGestureRecognizer:longPressGestureRecognizer];
}
else if (longPressGestureRecognizer)
{
[self.recentsTableView removeGestureRecognizer:longPressGestureRecognizer];
longPressGestureRecognizer = nil;
}
}
- (void)onRecentsDragEnd
{
[cellSnapshot removeFromSuperview];
cellSnapshot = nil;
movingCellPath = nil;
movingRoom = nil;
lastPotentialCellPath = nil;
((RecentsDataSource*)self.dataSource).droppingCellIndexPath = nil;
((RecentsDataSource*)self.dataSource).hiddenCellIndexPath = nil;
[self.activityIndicator stopAnimating];
}
- (IBAction)onRecentsLongPress:(id)sender
{
if (sender != longPressGestureRecognizer)
{
return;
}
RecentsDataSource* recentsDataSource = nil;
if ([self.dataSource isKindOfClass:[RecentsDataSource class]])
{
recentsDataSource = (RecentsDataSource*)self.dataSource;
}
// only support RecentsDataSource
if (!recentsDataSource)
{
return;
}
UIGestureRecognizerState state = longPressGestureRecognizer.state;
// check if there is a moving cell during the long press managemnt
if ((state != UIGestureRecognizerStateBegan) && !movingCellPath)
{
return;
}
CGPoint location = [longPressGestureRecognizer locationInView:self.recentsTableView];
switch (state)
{
// step 1 : display the selected cell
case UIGestureRecognizerStateBegan:
{
NSIndexPath *indexPath = [self.recentsTableView indexPathForRowAtPoint:location];
// check if the cell can be moved
if (indexPath && [recentsDataSource isDraggableCellAt:indexPath])
{
UITableViewCell *cell = [self.recentsTableView cellForRowAtIndexPath:indexPath];
cell.backgroundColor = ThemeService.shared.theme.backgroundColor;
// snapshot the cell
UIGraphicsBeginImageContextWithOptions(cell.bounds.size, NO, 0);
[cell.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
cellSnapshot = [[UIImageView alloc] initWithImage:image];
recentsDataSource.droppingCellBackGroundView = [[UIImageView alloc] initWithImage:image];
// display the selected cell over the tableview
CGPoint center = cell.center;
center.y = location.y;
cellSnapshot.center = center;
cellSnapshot.alpha = 0.5f;
[self.recentsTableView addSubview:cellSnapshot];
// Store the selected room and the original index path of its cell.
movingCellPath = indexPath;
movingRoom = [recentsDataSource getRoomAtIndexPath:movingCellPath];
lastPotentialCellPath = indexPath;
recentsDataSource.droppingCellIndexPath = indexPath;
recentsDataSource.hiddenCellIndexPath = indexPath;
}
break;
}
// step 2 : the cell must follow the finger
case UIGestureRecognizerStateChanged:
{
CGPoint center = cellSnapshot.center;
CGFloat halfHeight = cellSnapshot.frame.size.height / 2.0f;
CGFloat cellTop = location.y - halfHeight;
CGFloat cellBottom = location.y + halfHeight;
CGPoint contentOffset = self.recentsTableView.contentOffset;
CGFloat height = MIN(self.recentsTableView.frame.size.height, self.recentsTableView.contentSize.height);
CGFloat bottomOffset = contentOffset.y + height;
// check if the moving cell is trying to move under the tableview
if (cellBottom > self.recentsTableView.contentSize.height)
{
// force the cell to stay at the tableview bottom
location.y = self.recentsTableView.contentSize.height - halfHeight;
}
// check if the cell is moving over the displayed tableview bottom
else if (cellBottom > bottomOffset)
{
CGFloat diff = cellBottom - bottomOffset;
// moving down the cell
location.y -= diff;
// scroll up the tableview
contentOffset.y += diff;
}
// the moving is tryin to move over the tableview topmost
else if (cellTop < 0)
{
// force to stay in the topmost
contentOffset.y = 0;
location.y = contentOffset.y + halfHeight;
}
// the moving cell is displayed over the current scroll top
else if (cellTop < contentOffset.y)
{
CGFloat diff = contentOffset.y - cellTop;
// move up the cell and the table up
location.y -= diff;
contentOffset.y -= diff;
}
// move the cell to follow the user finger
center.y = location.y;
cellSnapshot.center = center;
// scroll the tableview if it is required
if (contentOffset.y != self.recentsTableView.contentOffset.y)
{
[self.recentsTableView setContentOffset:contentOffset animated:NO];
}
NSIndexPath *indexPath = [self.recentsTableView indexPathForRowAtPoint:location];
if (![indexPath isEqual:lastPotentialCellPath])
{
if ([recentsDataSource canCellMoveFrom:movingCellPath to:indexPath])
{
[self.recentsTableView beginUpdates];
if (recentsDataSource.droppingCellIndexPath && recentsDataSource.hiddenCellIndexPath)
{
[self.recentsTableView moveRowAtIndexPath:lastPotentialCellPath toIndexPath:indexPath];
}
else if (indexPath)
{
[self.recentsTableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self.recentsTableView deleteRowsAtIndexPaths:@[movingCellPath] withRowAnimation:UITableViewRowAnimationNone];
}
recentsDataSource.hiddenCellIndexPath = movingCellPath;
recentsDataSource.droppingCellIndexPath = indexPath;
[self.recentsTableView endUpdates];
}
// the cell cannot be moved
else if (recentsDataSource.droppingCellIndexPath)
{
NSIndexPath* pathToDelete = recentsDataSource.droppingCellIndexPath;
NSIndexPath* pathToAdd = recentsDataSource.hiddenCellIndexPath;
// remove it
[self.recentsTableView beginUpdates];
[self.recentsTableView deleteRowsAtIndexPaths:@[pathToDelete] withRowAnimation:UITableViewRowAnimationNone];
[self.recentsTableView insertRowsAtIndexPaths:@[pathToAdd] withRowAnimation:UITableViewRowAnimationNone];
recentsDataSource.droppingCellIndexPath = nil;
recentsDataSource.hiddenCellIndexPath = nil;
[self.recentsTableView endUpdates];
}
lastPotentialCellPath = indexPath;
}
break;
}
// step 3 : remove the view
// and insert when it is possible.
case UIGestureRecognizerStateEnded:
{
[cellSnapshot removeFromSuperview];
cellSnapshot = nil;
[self.activityIndicator startAnimating];
[recentsDataSource moveRoomCell:movingRoom from:movingCellPath to:lastPotentialCellPath success:^{
[self onRecentsDragEnd];
} failure:^(NSError *error) {
[self onRecentsDragEnd];
}];
break;
}
// default behaviour
// remove the cell and cancel the insertion
default:
{
[self onRecentsDragEnd];
break;
}
}
}
#pragma mark - Room handling
- (void)onPlusButtonPressed
{
__weak typeof(self) weakSelf = self;
[currentAlert dismissViewControllerAnimated:NO completion:nil];
currentAlert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomRecentsStartChatWith]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
[self performSegueWithIdentifier:@"presentStartChat" sender:self];
}
}]];
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomRecentsCreateEmptyRoom]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
[self createNewRoom];
}
}]];
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomRecentsJoinRoom]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
[self joinARoom];
}
}]];
if (self.mainSession.callManager.supportsPSTN)
{
[currentAlert addAction:[UIAlertAction
actionWithTitle:[VectorL10n roomOpenDialpad]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
[self openDialpad];
}
}]];
}
[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)openDialpad
{
DialpadViewController *controller = [DialpadViewController instantiateWithConfiguration:[DialpadConfiguration default]];
controller.delegate = self;
self.customSizedPresentationController = [[CustomSizedPresentationController alloc] initWithPresentedViewController:controller presentingViewController:self];
self.customSizedPresentationController.dismissOnBackgroundTap = NO;
self.customSizedPresentationController.cornerRadius = 16;
controller.transitioningDelegate = self.customSizedPresentationController;
[self presentViewController:controller animated:YES completion:nil];
}
- (void)dialpadViewControllerDidTapCall:(DialpadViewController *)viewController withPhoneNumber:(NSString *)phoneNumber
{
if (self.mainSession.callManager && phoneNumber.length > 0)
{
[self startActivityIndicator];
[viewController dismissViewControllerAnimated:YES completion:^{
MXWeakify(self);
[self.mainSession.callManager placeCallAgainst:phoneNumber withVideo:NO success:^(MXCall * _Nonnull call) {
MXStrongifyAndReturnIfNil(self);
[self stopActivityIndicator];
self.customSizedPresentationController = nil;
// do nothing extra here. UI will be handled automatically by the CallService.
} failure:^(NSError * _Nullable error) {
MXStrongifyAndReturnIfNil(self);
[self stopActivityIndicator];
}];
}];
}
}
- (void)dialpadViewControllerDidTapClose:(DialpadViewController *)viewController
{
[viewController dismissViewControllerAnimated:YES completion:nil];
self.customSizedPresentationController = nil;
}
- (void)createNewRoom
{
// Sanity check
if (self.mainSession)
{
self.createRoomCoordinatorBridgePresenter = [[CreateRoomCoordinatorBridgePresenter alloc] initWithSession:self.mainSession];
self.createRoomCoordinatorBridgePresenter.delegate = self;
[self.createRoomCoordinatorBridgePresenter presentFrom:self animated:YES];
}
}
- (void)joinARoom
{
[self showRoomDirectory];
}
- (void)showRoomDirectory
{
if (!self.self.mainSession)
{
MXLogDebug(@"[RecentsViewController] Fail to show room directory, session is nil");
return;
}
if (self.dataSource.currentSpace)
{
self.exploreRoomsCoordinatorBridgePresenter = [[ExploreRoomCoordinatorBridgePresenter alloc] initWithSession:self.mainSession spaceId:self.dataSource.currentSpace.spaceId];
self.exploreRoomsCoordinatorBridgePresenter.delegate = self;
[self.exploreRoomsCoordinatorBridgePresenter presentFrom:self animated:YES];
}
else if (RiotSettings.shared.roomsAllowToJoinPublicRooms)
{
self.roomsDirectoryCoordinatorBridgePresenter = [[RoomsDirectoryCoordinatorBridgePresenter alloc] initWithSession:self.mainSession dataSource:[self.recentsDataSource.publicRoomsDirectoryDataSource copy]];
self.roomsDirectoryCoordinatorBridgePresenter.delegate = self;
[self.roomsDirectoryCoordinatorBridgePresenter presentFrom:self animated:YES];
}
else
{
[self createNewRoom];
}
}
- (void)openPublicRoom:(MXPublicRoom *)publicRoom
{
if (!self.recentsDataSource)
{
MXLogDebug(@"[RecentsViewController] Fail to open public room, dataSource is not kind of class MXKRecentsDataSource");
return;
}
// Check whether the user has already joined the selected public room
if ([self.recentsDataSource.publicRoomsDirectoryDataSource.mxSession roomWithRoomId:publicRoom.roomId])
{
// Open the public room
[[AppDelegate theDelegate] showRoom:publicRoom.roomId andEventId:nil withMatrixSession:self.recentsDataSource.publicRoomsDirectoryDataSource.mxSession restoreInitialDisplay:NO];
}
else
{
// Preview the public room
if (publicRoom.worldReadable)
{
RoomPreviewData *roomPreviewData = [[RoomPreviewData alloc] initWithPublicRoom:publicRoom andSession:self.recentsDataSource.publicRoomsDirectoryDataSource.mxSession];
[self startActivityIndicator];
// Try to get more information about the room before opening its preview
[roomPreviewData peekInRoom:^(BOOL succeeded) {
[self stopActivityIndicator];
[[AppDelegate theDelegate].masterTabBarController showRoomPreview:roomPreviewData];
}];
}
else
{
RoomPreviewData *roomPreviewData = [[RoomPreviewData alloc] initWithPublicRoom:publicRoom andSession:self.recentsDataSource.publicRoomsDirectoryDataSource.mxSession];
[[AppDelegate theDelegate].masterTabBarController showRoomPreview:roomPreviewData];
}
}
}
#pragma mark - Table view scrolling
- (void)scrollToTop:(BOOL)animated
{
[self.recentsTableView setContentOffset:CGPointMake(-self.recentsTableView.adjustedContentInset.left, -self.recentsTableView.adjustedContentInset.top) animated:animated];
}
- (void)scrollToTheTopTheNextRoomWithMissedNotificationsInSection:(NSInteger)section
{
UITableViewCell *firstVisibleCell;
NSIndexPath *firstVisibleCellIndexPath;
UIView *firstSectionHeader = displayedSectionHeaders.firstObject;
if (firstSectionHeader && firstSectionHeader.frame.origin.y <= self.recentsTableView.contentOffset.y)
{
// Compute the height of the hidden part of the section header.
CGFloat hiddenPart = self.recentsTableView.contentOffset.y - firstSectionHeader.frame.origin.y;
CGFloat firstVisibleCellPosY = self.recentsTableView.contentOffset.y + (firstSectionHeader.frame.size.height - hiddenPart);
firstVisibleCellIndexPath = [self.recentsTableView indexPathForRowAtPoint:CGPointMake(0, firstVisibleCellPosY)];
firstVisibleCell = [self.recentsTableView cellForRowAtIndexPath:firstVisibleCellIndexPath];
}
else
{
firstVisibleCell = self.recentsTableView.visibleCells.firstObject;
firstVisibleCellIndexPath = [self.recentsTableView indexPathForCell:firstVisibleCell];
}
if (firstVisibleCell)
{
NSInteger nextCellRow = (firstVisibleCellIndexPath.section == section) ? firstVisibleCellIndexPath.row + 1 : 0;
// Look for the next room with missed notifications.
NSIndexPath *nextIndexPath = [NSIndexPath indexPathForRow:nextCellRow inSection:section];
nextCellRow++;
id<MXKRecentCellDataStoring> cellData = [self.dataSource cellDataAtIndexPath:nextIndexPath];
while (cellData)
{
if (cellData.notificationCount)
{
[self.recentsTableView scrollToRowAtIndexPath:nextIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
break;
}
nextIndexPath = [NSIndexPath indexPathForRow:nextCellRow inSection:section];
nextCellRow++;
cellData = [self.dataSource cellDataAtIndexPath:nextIndexPath];
}
if (!cellData && [self.recentsTableView numberOfRowsInSection:section] > 0)
{
// Scroll back to the top.
[self.recentsTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
}
}
#pragma mark - MXKRecentListViewControllerDelegate
- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController didSelectRoom:(NSString *)roomId inMatrixSession:(MXSession *)matrixSession
{
[self dispayRoomWithRoomId:roomId inMatrixSession:matrixSession];
}
- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController didSelectSuggestedRoom:(MXSpaceChildInfo *)childInfo
{
RoomPreviewData *previewData = [[RoomPreviewData alloc] initWithSpaceChildInfo:childInfo andSession:self.mainSession];
[self startActivityIndicator];
MXWeakify(self);
[previewData peekInRoom:^(BOOL succeeded) {
MXStrongifyAndReturnIfNil(self);
[self stopActivityIndicator];
[[AppDelegate theDelegate].masterTabBarController showRoomPreview:previewData];
}];
}
#pragma mark - UISearchBarDelegate
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
[super scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
}
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
{
if (searchBar == tableSearchBar)
{
[self hideSearchBar:NO];
[self.recentsSearchBar becomeFirstResponder];
return NO;
}
return YES;
}
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
dispatch_async(dispatch_get_main_queue(), ^{
[self.recentsSearchBar setShowsCancelButton:YES animated:NO];
});
}
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
[self.recentsSearchBar setShowsCancelButton:NO animated:NO];
}
#pragma mark - CreateRoomCoordinatorBridgePresenterDelegate
- (void)createRoomCoordinatorBridgePresenterDelegate:(CreateRoomCoordinatorBridgePresenter *)coordinatorBridgePresenter didCreateNewRoom:(MXRoom *)room
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
[[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession restoreInitialDisplay:NO];
}];
coordinatorBridgePresenter = nil;
}
- (void)createRoomCoordinatorBridgePresenterDelegateDidCancel:(CreateRoomCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
coordinatorBridgePresenter = nil;
}
#pragma mark - Empty view management
- (void)showEmptyViewIfNeeded
{
[self showEmptyView:[self shouldShowEmptyView]];
}
- (void)showEmptyView:(BOOL)show
{
if (!self.viewIfLoaded)
{
return;
}
if (show && !self.emptyView)
{
RootTabEmptyView *emptyView = [RootTabEmptyView instantiate];
[emptyView updateWithTheme:ThemeService.shared.theme];
[self addEmptyView:emptyView];
self.emptyView = emptyView;
[self updateEmptyView];
}
else if (!show)
{
[self.emptyView removeFromSuperview];
}
self.recentsTableView.hidden = show;
self.stickyHeadersTopContainer.hidden = show;
self.stickyHeadersBottomContainer.hidden = show;
}
- (void)updateEmptyView
{
}
- (void)addEmptyView:(RootTabEmptyView*)emptyView
{
if (!self.isViewLoaded)
{
return;
}
NSLayoutConstraint *emptyViewBottomConstraint;
NSLayoutConstraint *contentViewBottomConstraint;
if (plusButtonImageView && plusButtonImageView.isHidden == NO)
{
[self.view insertSubview:emptyView belowSubview:plusButtonImageView];
contentViewBottomConstraint = [NSLayoutConstraint constraintWithItem:emptyView.contentView
attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationLessThanOrEqual toItem:plusButtonImageView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0];
}
else
{
[self.view addSubview:emptyView];
}
emptyViewBottomConstraint = [emptyView.bottomAnchor constraintEqualToAnchor:emptyView.superview.bottomAnchor];
emptyView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[emptyView.topAnchor constraintEqualToAnchor:emptyView.superview.topAnchor],
[emptyView.leftAnchor constraintEqualToAnchor:emptyView.superview.leftAnchor],
[emptyView.rightAnchor constraintEqualToAnchor:emptyView.superview.rightAnchor],
emptyViewBottomConstraint
]];
if (contentViewBottomConstraint)
{
contentViewBottomConstraint.active = YES;
}
}
- (BOOL)shouldShowEmptyView
{
return NO;
}
#pragma mark - RoomsDirectoryCoordinatorBridgePresenterDelegate
- (void)roomsDirectoryCoordinatorBridgePresenterDelegateDidComplete:(RoomsDirectoryCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
self.roomsDirectoryCoordinatorBridgePresenter = nil;
}
- (void)roomsDirectoryCoordinatorBridgePresenterDelegate:(RoomsDirectoryCoordinatorBridgePresenter *)coordinatorBridgePresenter didSelectRoom:(MXPublicRoom *)room
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
[self openPublicRoom:room];
}];
self.roomsDirectoryCoordinatorBridgePresenter = nil;
}
- (void)roomsDirectoryCoordinatorBridgePresenterDelegateDidTapCreateNewRoom:(RoomsDirectoryCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
[self createNewRoom];
}];
self.roomsDirectoryCoordinatorBridgePresenter = nil;
}
- (void)roomsDirectoryCoordinatorBridgePresenterDelegate:(RoomsDirectoryCoordinatorBridgePresenter *)coordinatorBridgePresenter didSelectRoomWithIdOrAlias:(NSString * _Nonnull)roomIdOrAlias
{
MXRoom *room = [self.mainSession vc_roomWithIdOrAlias:roomIdOrAlias];
if (room)
{
// Room is known show it directly
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
[[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession restoreInitialDisplay:NO];
}];
coordinatorBridgePresenter = nil;
}
else if ([MXTools isMatrixRoomAlias:roomIdOrAlias])
{
// Room preview doesn't support room alias
[[AppDelegate theDelegate] showAlertWithTitle:[MatrixKitL10n error] message:[VectorL10n roomRecentsUnknownRoomErrorMessage]];
}
else
{
// Try to preview the room from his id
RoomPreviewData *roomPreviewData = [[RoomPreviewData alloc] initWithRoomId:roomIdOrAlias
andSession:self.mainSession];
[self startActivityIndicator];
// Try to get more information about the room before opening its preview
MXWeakify(self);
[roomPreviewData peekInRoom:^(BOOL succeeded) {
MXStrongifyAndReturnIfNil(self);
[self stopActivityIndicator];
if (succeeded) {
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
[[AppDelegate theDelegate].masterTabBarController showRoomPreview:roomPreviewData];
}];
self.roomsDirectoryCoordinatorBridgePresenter = nil;
} else {
[[AppDelegate theDelegate] showAlertWithTitle:[MatrixKitL10n error] message:[VectorL10n roomRecentsUnknownRoomErrorMessage]];
}
}];
}
}
#pragma mark - ExploreRoomCoordinatorBridgePresenterDelegate
- (void)exploreRoomCoordinatorBridgePresenterDelegateDidComplete:(ExploreRoomCoordinatorBridgePresenter *)coordinatorBridgePresenter {
MXWeakify(self);
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
MXStrongifyAndReturnIfNil(self);
self.exploreRoomsCoordinatorBridgePresenter = nil;
}];
}
#pragma mark - RoomNotificationSettingsCoordinatorBridgePresenterDelegate
-(void)roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete:(RoomNotificationSettingsCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
self.roomNotificationSettingsCoordinatorBridgePresenter = nil;
}
@end