2478 lines
95 KiB
Objective-C
2478 lines
95 KiB
Objective-C
/*
|
|
Copyright 2018-2024 New Vector Ltd.
|
|
Copyright 2017 Vector Creations Ltd
|
|
Copyright 2015 OpenMarket Ltd
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
#import "RecentsViewController.h"
|
|
#import "RecentsDataSource.h"
|
|
#import "RecentTableViewCell.h"
|
|
|
|
#import "UnifiedSearchViewController.h"
|
|
|
|
#import "MXRoom+Riot.h"
|
|
|
|
#import "RoomViewController.h"
|
|
|
|
#import "InviteRecentTableViewCell.h"
|
|
#import "DirectoryRecentTableViewCell.h"
|
|
#import "RoomIdOrAliasTableViewCell.h"
|
|
#import "TableViewCellWithCollectionView.h"
|
|
#import "SectionHeaderView.h"
|
|
|
|
#import "GeneratedInterface-Swift.h"
|
|
|
|
NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewControllerDataReadyNotification";
|
|
|
|
@interface RecentsViewController () <CreateRoomCoordinatorBridgePresenterDelegate, RoomsDirectoryCoordinatorBridgePresenterDelegate, RoomNotificationSettingsCoordinatorBridgePresenterDelegate, DialpadViewControllerDelegate, ExploreRoomCoordinatorBridgePresenterDelegate, SpaceChildRoomDetailBridgePresenterDelegate, RoomContextActionServiceDelegate, RecentCellContextMenuProviderDelegate>
|
|
{
|
|
// 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.
|
|
__weak id UIApplicationDidEnterBackgroundNotificationObserver;
|
|
|
|
// Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar.
|
|
__weak id kAppDelegateDidTapStatusBarNotificationObserver;
|
|
|
|
// Observe kMXNotificationCenterDidUpdateRules to update missed messages counts.
|
|
__weak 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;
|
|
|
|
// Flag indicating whether the view controller is (at least partially) visible and not dissapearing
|
|
BOOL isViewVisible;
|
|
|
|
// Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change.
|
|
__weak id kThemeServiceDidChangeThemeNotificationObserver;
|
|
|
|
// Cancel handler of any ongoing loading indicator
|
|
UserIndicatorCancel loadingIndicatorCancel;
|
|
}
|
|
|
|
@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;
|
|
|
|
@property (nonatomic, strong) SpaceChildRoomDetailBridgePresenter *spaceChildPresenter;
|
|
|
|
@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];
|
|
|
|
// 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 searchFilterPlaceholder];
|
|
[tableSearchBar setImage:AssetImages.filterOff.image
|
|
forSearchBarIcon:UISearchBarIconSearch
|
|
state:UIControlStateNormal];
|
|
|
|
tableSearchBar.delegate = self;
|
|
|
|
displayedSectionHeaders = [NSMutableArray array];
|
|
|
|
_contextMenuProvider = [RecentCellContextMenuProvider new];
|
|
self.contextMenuProvider.serviceDelegate = self;
|
|
self.contextMenuProvider.menuProviderDelegate = self;
|
|
|
|
// 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];
|
|
|
|
[self.recentsTableView registerClass:SectionHeaderView.class
|
|
forHeaderFooterViewReuseIdentifier:SectionHeaderView.defaultReuseIdentifier];
|
|
|
|
// Hide line separators of empty cells
|
|
self.recentsTableView.tableFooterView = [[UIView alloc] init];
|
|
|
|
// Apply dragging settings
|
|
self.enableDragging = _enableDragging;
|
|
|
|
MXWeakify(self);
|
|
|
|
// Observe UIApplicationDidEnterBackgroundNotification to refresh bubbles when app leaves the foreground state.
|
|
UIApplicationDidEnterBackgroundNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
// Leave potential editing mode
|
|
[self cancelEditionMode:self->isRefreshPending];
|
|
|
|
}];
|
|
|
|
self.recentsSearchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
|
|
self.recentsSearchBar.placeholder = [VectorL10n searchFilterPlaceholder];
|
|
[self.recentsSearchBar setImage:AssetImages.filterOff.image
|
|
forSearchBarIcon:UISearchBarIconSearch
|
|
state:UIControlStateNormal];
|
|
|
|
// Observe user interface theme change.
|
|
kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
[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];
|
|
|
|
// Force table refresh
|
|
[self.recentsTableView reloadData];
|
|
|
|
[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];
|
|
isViewVisible = YES;
|
|
|
|
[self.screenTracker trackScreen];
|
|
|
|
// 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];
|
|
}
|
|
|
|
MXWeakify(self);
|
|
|
|
// Observe kAppDelegateDidTapStatusBarNotificationObserver.
|
|
kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
[self scrollToTop:YES];
|
|
|
|
}];
|
|
|
|
// Observe kMXNotificationCenterDidUpdateRules to refresh missed messages counts
|
|
kMXNotificationCenterDidUpdateRulesObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXNotificationCenterDidUpdateRules object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
[self refreshRecentsTable];
|
|
|
|
}];
|
|
}
|
|
|
|
- (void)viewWillDisappear:(BOOL)animated
|
|
{
|
|
[super viewWillDisappear:animated];
|
|
isViewVisible = NO;
|
|
|
|
// Leave potential editing mode
|
|
[self cancelEditionMode:NO];
|
|
|
|
if (kAppDelegateDidTapStatusBarNotificationObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:kAppDelegateDidTapStatusBarNotificationObserver];
|
|
kAppDelegateDidTapStatusBarNotificationObserver = nil;
|
|
}
|
|
|
|
if (kMXNotificationCenterDidUpdateRulesObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:kMXNotificationCenterDidUpdateRulesObserver];
|
|
kMXNotificationCenterDidUpdateRulesObserver = nil;
|
|
}
|
|
|
|
[self stopActivityIndicator];
|
|
}
|
|
|
|
- (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];
|
|
}
|
|
|
|
if (self.recentsDataSource)
|
|
{
|
|
[self refreshRecentsTable];
|
|
[self showEmptyViewIfNeeded];
|
|
}
|
|
}
|
|
|
|
- (void)viewDidLayoutSubviews
|
|
{
|
|
[super viewDidLayoutSubviews];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
[self refreshStickyHeadersContainersHeight];
|
|
|
|
});
|
|
}
|
|
|
|
#pragma mark - Override MXKRecentListViewController
|
|
|
|
- (void)refreshRecentsTable
|
|
{
|
|
if (!self.recentsUpdateEnabled)
|
|
{
|
|
isRefreshNeeded = YES;
|
|
return;
|
|
}
|
|
|
|
isRefreshNeeded = NO;
|
|
|
|
// Refresh the tabBar icon badges
|
|
if (!BuildSettings.newAppLayoutEnabled)
|
|
{
|
|
// 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 = [VectorL10n roomErrorJoinFailedEmptyRoom];
|
|
}
|
|
|
|
[self->currentAlert dismissViewControllerAnimated:NO completion:nil];
|
|
|
|
UIAlertController *errorAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomErrorJoinFailedTitle]
|
|
message:msg
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
|
|
[errorAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok]
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(UIAlertAction * action) {
|
|
MXStrongifyAndReturnIfNil(self);
|
|
self->currentAlert = nil;
|
|
|
|
if (completion)
|
|
{
|
|
completion();
|
|
}
|
|
}]];
|
|
|
|
[self presentViewController:errorAlert animated:YES completion:nil];
|
|
currentAlert = errorAlert;
|
|
}
|
|
|
|
#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)showRoomWithRoomId:(NSString*)roomId inMatrixSession:(MXSession*)matrixSession
|
|
{
|
|
[self showRoomWithRoomId:roomId andAutoJoinInvitedRoom:false inMatrixSession:matrixSession];
|
|
}
|
|
|
|
- (void)showRoomWithRoomId:(NSString*)roomId andAutoJoinInvitedRoom:(BOOL)autoJoinInvitedRoom inMatrixSession:(MXSession*)matrixSession
|
|
{
|
|
MXRoom *room = [matrixSession roomWithRoomId:roomId];
|
|
if (room.summary.membership == MXMembershipInvite)
|
|
{
|
|
Analytics.shared.joinedRoomTrigger = AnalyticsJoinedRoomTriggerInvite;
|
|
}
|
|
|
|
// Avoid multiple openings of rooms
|
|
self.userInteractionEnabled = NO;
|
|
|
|
// Do not stack views when showing room
|
|
ScreenPresentationParameters *presentationParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:NO stackAboveVisibleViews:NO];
|
|
|
|
RoomNavigationParameters *parameters = [[RoomNavigationParameters alloc] initWithRoomId:roomId
|
|
eventId:nil
|
|
mxSession:matrixSession
|
|
threadParameters:nil
|
|
presentationParameters:presentationParameters
|
|
autoJoinInvitedRoom:autoJoinInvitedRoom];
|
|
|
|
[[AppDelegate theDelegate] showRoomWithParameters:parameters completion:^{
|
|
self.userInteractionEnabled = YES;
|
|
}];
|
|
}
|
|
|
|
- (void)showRoomPreviewWithData:(RoomPreviewData*)roomPreviewData
|
|
{
|
|
Analytics.shared.joinedRoomTrigger = AnalyticsJoinedRoomTriggerRoomDirectory;
|
|
|
|
// Do not stack views when showing room
|
|
ScreenPresentationParameters *presentationParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:NO stackAboveVisibleViews:NO sender:nil sourceView:nil];
|
|
|
|
RoomPreviewNavigationParameters *parameters = [[RoomPreviewNavigationParameters alloc] initWithPreviewData:roomPreviewData presentationParameters:presentationParameters];
|
|
|
|
[[AppDelegate theDelegate] showRoomPreviewWithParameters:parameters];
|
|
}
|
|
|
|
// 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.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 showRoomWithRoomId: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 and display the room
|
|
Analytics.shared.joinedRoomTrigger = AnalyticsJoinedRoomTriggerInvite;
|
|
[self showRoomWithRoomId:invitedRoom.roomId andAutoJoinInvitedRoom:true inMatrixSession:invitedRoom.mxSession];
|
|
}
|
|
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
|
|
{
|
|
if (!self.recentsUpdateEnabled)
|
|
{
|
|
[super dataSource:dataSource didCellChange:changes];
|
|
return;
|
|
}
|
|
|
|
if ([changes isKindOfClass:NSIndexPath.class])
|
|
{
|
|
NSIndexPath *indexPath = (NSIndexPath *)changes;
|
|
UITableViewCell *cell = [self.recentsTableView cellForRowAtIndexPath:indexPath];
|
|
if ([cell isKindOfClass:TableViewCellWithCollectionView.class])
|
|
{
|
|
MXLogDebug(@"[RecentsViewController]: Reloading nested collection view cell in section %ld", indexPath.section);
|
|
|
|
TableViewCellWithCollectionView *collectionViewCell = (TableViewCellWithCollectionView *)cell;
|
|
[collectionViewCell.collectionView reloadData];
|
|
|
|
CGRect headerFrame = [self.recentsTableView rectForHeaderInSection:indexPath.section];
|
|
UIView *headerView = [self.recentsTableView headerViewForSection:indexPath.section];
|
|
UIView *updatedHeaderView = [self.dataSource viewForHeaderInSection:indexPath.section withFrame:headerFrame inTableView:self.recentsTableView];
|
|
if ([headerView isKindOfClass:SectionHeaderView.class]
|
|
&& [updatedHeaderView isKindOfClass:SectionHeaderView.class])
|
|
{
|
|
SectionHeaderView *sectionHeaderView = (SectionHeaderView *)headerView;
|
|
SectionHeaderView *updatedSectionHeaderView = (SectionHeaderView *)updatedHeaderView;
|
|
sectionHeaderView.headerLabel = updatedSectionHeaderView.headerLabel;
|
|
sectionHeaderView.accessoryView = updatedSectionHeaderView.accessoryView;
|
|
sectionHeaderView.rightAccessoryView = updatedSectionHeaderView.rightAccessoryView;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Ideally we would call tableView.reloadSections, but this can lead to crashes if multiple sections need such an update and they
|
|
// vertically depend on each other. It is unclear whether this is due to further issues in the data model (e.g. data race)
|
|
// or some undocumented table view behavior. To avoid this we reload the entire table view, even if this means reloading
|
|
// multiple times for several section updates.
|
|
MXLogDebug(@"[RecentsViewController]: Reloading the entire table view due to updates in section %ld", indexPath.section);
|
|
[self refreshRecentsTable];
|
|
}
|
|
}
|
|
else if (!changes)
|
|
{
|
|
MXLogDebug(@"[RecentsViewController]: Reloading the entire table view");
|
|
[self refreshRecentsTable];
|
|
}
|
|
|
|
if (!BuildSettings.newAppLayoutEnabled)
|
|
{
|
|
// Since we've enabled room list pagination, `refreshRecentsTable` not called in this case.
|
|
// Refresh tab bar badges separately.
|
|
[[AppDelegate theDelegate].masterTabBarController refreshTabBarBadges];
|
|
}
|
|
|
|
[self showEmptyViewIfNeeded];
|
|
|
|
if (dataSource.state == MXKDataSourceStateReady)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RecentsViewControllerDataReadyNotification
|
|
object:self];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Swipe actions
|
|
|
|
- (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
|
|
UIAlertController *leavePrompt = [UIAlertController alertControllerWithTitle:title
|
|
message:message
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
|
|
[leavePrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
|
|
style:UIAlertActionStyleCancel
|
|
handler:^(UIAlertAction * action) {
|
|
|
|
if (weakSelf)
|
|
{
|
|
typeof(self) self = weakSelf;
|
|
self->currentAlert = nil;
|
|
}
|
|
|
|
}]];
|
|
|
|
[leavePrompt 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 startActivityIndicatorWithLabel:[VectorL10n roomParticipantsLeaveProcessing]];
|
|
// 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];
|
|
[self.userIndicatorStore presentSuccessWithLabel:[VectorL10n roomParticipantsLeaveSuccess]];
|
|
// 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:self->isRefreshPending];
|
|
}
|
|
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
// Leave editing mode
|
|
[self cancelEditionMode:self->isRefreshPending];
|
|
}
|
|
}
|
|
|
|
}]];
|
|
|
|
[leavePrompt mxk_setAccessibilityIdentifier:@"LeaveEditedRoomAlert"];
|
|
[self presentViewController:leavePrompt animated:YES completion:nil];
|
|
currentAlert = leavePrompt;
|
|
}
|
|
}
|
|
|
|
- (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)
|
|
{
|
|
// Check whether the user didn't leave the room
|
|
// TODO: handle multi-account
|
|
MXRoom *room = [self.mainSession roomWithRoomId:editedRoomId];
|
|
if (room)
|
|
{
|
|
[self startActivityIndicator];
|
|
|
|
MXWeakify(self);
|
|
|
|
[room setIsDirect:isDirect withUserId:nil success:^{
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
[self stopActivityIndicator];
|
|
// Leave editing mode
|
|
[self cancelEditionMode:self->isRefreshPending];
|
|
|
|
} failure:^(NSError *error) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
[self stopActivityIndicator];
|
|
|
|
MXLogDebug(@"[RecentsViewController] Failed to update direct tag of the room (%@)", self->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:self->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
|
|
|
|
if (cellData.roomSummary.roomType == MXRoomTypeSpace)
|
|
{
|
|
// Indicates that spaces are not supported
|
|
[self showSpaceInviteNotAvailable];
|
|
}
|
|
// Check if can show preview for the invited room
|
|
else if ([self canShowRoomPreviewFor:cellData.roomSummary])
|
|
{
|
|
// Display the room preview
|
|
[self showRoomWithRoomId:cellData.roomIdentifier inMatrixSession:cellData.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)
|
|
{
|
|
// Create a permalink to open or preview the room.
|
|
NSString *permalink = [MXTools permalinkToRoom:roomIdOrAlias];
|
|
NSURL *permalinkURL = [NSURL URLWithString:permalink];
|
|
[[AppDelegate theDelegate] handleUniversalLinkURL:permalinkURL];
|
|
}
|
|
[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];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath {
|
|
return [VectorL10n leave];
|
|
}
|
|
|
|
#pragma mark - UIScrollViewDelegate
|
|
|
|
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
|
{
|
|
if (!self.recentsSearchBar)
|
|
{
|
|
[super scrollViewDidScroll:scrollView];
|
|
return;
|
|
}
|
|
|
|
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];
|
|
|
|
UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
|
|
|
|
[actionSheet addAction:[UIAlertAction actionWithTitle:[VectorL10n roomRecentsStartChatWith]
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(UIAlertAction * action) {
|
|
|
|
if (weakSelf)
|
|
{
|
|
typeof(self) self = weakSelf;
|
|
self->currentAlert = nil;
|
|
|
|
[self startChat];
|
|
}
|
|
|
|
}]];
|
|
|
|
[actionSheet addAction:[UIAlertAction actionWithTitle:[VectorL10n roomRecentsCreateEmptyRoom]
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(UIAlertAction * action) {
|
|
|
|
if (weakSelf)
|
|
{
|
|
typeof(self) self = weakSelf;
|
|
self->currentAlert = nil;
|
|
|
|
[self createNewRoom];
|
|
}
|
|
|
|
}]];
|
|
|
|
[actionSheet 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)
|
|
{
|
|
[actionSheet addAction:[UIAlertAction actionWithTitle:[VectorL10n roomOpenDialpad]
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(UIAlertAction * action) {
|
|
|
|
if (weakSelf)
|
|
{
|
|
typeof(self) self = weakSelf;
|
|
self->currentAlert = nil;
|
|
|
|
[self openDialpad];
|
|
}
|
|
|
|
}]];
|
|
}
|
|
|
|
[actionSheet addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
|
|
style:UIAlertActionStyleCancel
|
|
handler:^(UIAlertAction * action) {
|
|
|
|
if (weakSelf)
|
|
{
|
|
typeof(self) self = weakSelf;
|
|
self->currentAlert = nil;
|
|
}
|
|
|
|
}]];
|
|
|
|
[actionSheet popoverPresentationController].sourceView = plusButtonImageView;
|
|
[actionSheet popoverPresentationController].sourceRect = plusButtonImageView.bounds;
|
|
|
|
[actionSheet mxk_setAccessibilityIdentifier:@"RecentsVCCreateRoomAlert"];
|
|
[self presentViewController:actionSheet animated:YES completion:nil];
|
|
currentAlert = actionSheet;
|
|
}
|
|
|
|
- (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)startChat {
|
|
[self performSegueWithIdentifier:@"presentStartChat" sender:self];
|
|
}
|
|
|
|
- (void)createNewRoom
|
|
{
|
|
// Sanity check
|
|
if (self.mainSession)
|
|
{
|
|
CreateRoomCoordinatorParameter *parameters = [[CreateRoomCoordinatorParameter alloc] initWithSession:self.mainSession parentSpace: self.dataSource.currentSpace];
|
|
self.createRoomCoordinatorBridgePresenter = [[CreateRoomCoordinatorBridgePresenter alloc] initWithParameters:parameters];
|
|
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 isJoinedOnRoom:publicRoom.roomId])
|
|
{
|
|
Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerRoomDirectory;
|
|
|
|
// Open the public room
|
|
[self showRoomWithRoomId:publicRoom.roomId
|
|
inMatrixSession:self.recentsDataSource.publicRoomsDirectoryDataSource.mxSession];
|
|
}
|
|
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];
|
|
|
|
[self showRoomPreviewWithData:roomPreviewData];
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
RoomPreviewData *roomPreviewData = [[RoomPreviewData alloc] initWithPublicRoom:publicRoom andSession:self.recentsDataSource.publicRoomsDirectoryDataSource.mxSession];
|
|
|
|
[self showRoomPreviewWithData: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
|
|
{
|
|
if (section < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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 && section < self.recentsTableView.numberOfSections && [self.recentsTableView numberOfRowsInSection:section] > 0)
|
|
{
|
|
// Scroll back to the top.
|
|
[self.recentsTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section] atScrollPosition:UITableViewScrollPositionTop animated:YES];
|
|
}
|
|
else if (section >= self.recentsTableView.numberOfSections)
|
|
{
|
|
NSDictionary *details = @{
|
|
@"section": @(section),
|
|
@"number_of_sections": @(self.recentsTableView.numberOfSections)
|
|
};
|
|
MXLogFailureDetails(@"[RecentsViewController] Section in a table view is invalid", details);
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark - MXKRecentListViewControllerDelegate
|
|
|
|
- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController didSelectRoom:(NSString *)roomId inMatrixSession:(MXSession *)matrixSession
|
|
{
|
|
Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerRoomList;
|
|
[self showRoomWithRoomId:roomId inMatrixSession:matrixSession];
|
|
}
|
|
|
|
- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController didSelectSuggestedRoom:(MXSpaceChildInfo *)childInfo from:(UIView* _Nullable)sourceView
|
|
{
|
|
Analytics.shared.joinedRoomTrigger = AnalyticsJoinedRoomTriggerSpaceHierarchy;
|
|
|
|
self.spaceChildPresenter = [[SpaceChildRoomDetailBridgePresenter alloc] initWithSession:self.mainSession childInfo:childInfo];
|
|
self.spaceChildPresenter.delegate = self;
|
|
[self.spaceChildPresenter presentFrom:self sourceView:sourceView animated:YES];
|
|
}
|
|
|
|
#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];
|
|
}
|
|
|
|
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
|
|
{
|
|
[super searchBar:searchBar textDidChange:searchText];
|
|
|
|
UIImage *filterIcon = searchText.length > 0 ? AssetImages.filterOn.image : AssetImages.filterOff.image;
|
|
[self.recentsSearchBar setImage:filterIcon
|
|
forSearchBarIcon:UISearchBarIconSearch
|
|
state:UIControlStateNormal];
|
|
}
|
|
|
|
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
|
|
{
|
|
[self.recentsSearchBar resignFirstResponder];
|
|
[self hideSearchBar:YES];
|
|
self.recentsTableView.contentOffset = CGPointMake(0, self.recentsSearchBar.frame.size.height);
|
|
self.recentsTableView.tableHeaderView = nil;
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
[self.recentsDataSource searchWithPatterns:nil];
|
|
[self.recentsSearchBar setText:nil];
|
|
});
|
|
}
|
|
|
|
#pragma mark - CreateRoomCoordinatorBridgePresenterDelegate
|
|
|
|
- (void)createRoomCoordinatorBridgePresenterDelegate:(CreateRoomCoordinatorBridgePresenter *)coordinatorBridgePresenter didCreateNewRoom:(MXRoom *)room
|
|
{
|
|
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
|
|
Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerCreated;
|
|
[self showRoomWithRoomId:room.roomId inMatrixSession:self.mainSession];
|
|
}];
|
|
coordinatorBridgePresenter = nil;
|
|
}
|
|
|
|
- (void)createRoomCoordinatorBridgePresenterDelegateDidCancel:(CreateRoomCoordinatorBridgePresenter *)coordinatorBridgePresenter
|
|
{
|
|
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
|
|
coordinatorBridgePresenter = nil;
|
|
}
|
|
|
|
- (void)createRoomCoordinatorBridgePresenterDelegate:(CreateRoomCoordinatorBridgePresenter *)coordinatorBridgePresenter didAddRoomsWithIds:(NSArray<NSString *> *)roomIds
|
|
{
|
|
[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];
|
|
}
|
|
|
|
NSLayoutYAxisAnchor *bottomAnchor = self.emptyViewBottomAnchor ?: emptyView.superview.bottomAnchor;
|
|
emptyViewBottomConstraint = [emptyView.bottomAnchor constraintEqualToAnchor:bottomAnchor constant:-1]; // 1pt spacing for UIToolbar's divider.
|
|
|
|
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
|
|
{
|
|
// Do not present empty screen while searching
|
|
if (self.recentsDataSource.searchPatternsList.count)
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
return self.recentsDataSource.totalVisibleItemCount == 0;
|
|
}
|
|
|
|
#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:^{
|
|
[self showRoomWithRoomId:room.roomId
|
|
inMatrixSession:self.mainSession];
|
|
}];
|
|
coordinatorBridgePresenter = nil;
|
|
}
|
|
else if ([MXTools isMatrixRoomAlias:roomIdOrAlias])
|
|
{
|
|
// Room preview doesn't support room alias
|
|
[[AppDelegate theDelegate] showAlertWithTitle:[VectorL10n 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:^{
|
|
|
|
[self showRoomPreviewWithData:roomPreviewData];
|
|
}];
|
|
self.roomsDirectoryCoordinatorBridgePresenter = nil;
|
|
} else {
|
|
[[AppDelegate theDelegate] showAlertWithTitle:[VectorL10n 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;
|
|
}
|
|
|
|
#pragma mark - SpaceChildRoomDetailBridgePresenterDelegate
|
|
- (void)spaceChildRoomDetailBridgePresenterDidCancel:(SpaceChildRoomDetailBridgePresenter *)coordinator
|
|
{
|
|
[self.spaceChildPresenter dismissWithAnimated:YES completion:^{
|
|
self.spaceChildPresenter = nil;
|
|
}];
|
|
}
|
|
|
|
- (void)spaceChildRoomDetailBridgePresenter:(SpaceChildRoomDetailBridgePresenter *)coordinator didOpenRoomWith:(NSString *)roomId
|
|
{
|
|
[self showRoomWithRoomId:roomId inMatrixSession:self.mainSession];
|
|
|
|
[self.spaceChildPresenter dismissWithAnimated:YES completion:^{
|
|
self.spaceChildPresenter = nil;
|
|
}];
|
|
}
|
|
|
|
#pragma mark - Activity Indicator
|
|
|
|
- (BOOL)providesCustomActivityIndicator {
|
|
return self.userIndicatorStore != nil;
|
|
}
|
|
|
|
- (void)startActivityIndicatorWithLabel:(NSString *)label {
|
|
if (self.userIndicatorStore && isViewVisible) {
|
|
// The app is very liberal with calling `startActivityIndicator` (often not matched by corresponding `stopActivityIndicator`),
|
|
// so there is no reason to keep adding new indicators if there is one already showing.
|
|
if (loadingIndicatorCancel) {
|
|
return;
|
|
}
|
|
|
|
MXLogDebug(@"[RecentsViewController] Present loading indicator")
|
|
loadingIndicatorCancel = [self.userIndicatorStore presentLoadingWithLabel:label isInteractionBlocking:NO];
|
|
} else {
|
|
[super startActivityIndicator];
|
|
}
|
|
}
|
|
|
|
- (void)startActivityIndicator {
|
|
[self startActivityIndicatorWithLabel:[VectorL10n homeSyncing]];
|
|
}
|
|
|
|
- (void)stopActivityIndicator {
|
|
if (self.userIndicatorStore) {
|
|
if (loadingIndicatorCancel) {
|
|
MXLogDebug(@"[RecentsViewController] Dismiss loading indicator")
|
|
loadingIndicatorCancel();
|
|
loadingIndicatorCancel = nil;
|
|
}
|
|
} else {
|
|
[super stopActivityIndicator];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Context Menu
|
|
|
|
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point API_AVAILABLE(ios(13.0))
|
|
{
|
|
id<MXKRecentCellDataStoring> cellData = [self.dataSource cellDataAtIndexPath:indexPath];
|
|
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
|
|
|
|
if (!cellData || !cell)
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
return [self.contextMenuProvider contextMenuConfigurationWith:cellData from:cell session:self.dataSource.mxSession];
|
|
}
|
|
|
|
- (void)tableView:(UITableView *)tableView willPerformPreviewActionForMenuWithConfiguration:(UIContextMenuConfiguration *)configuration animator:(id<UIContextMenuInteractionCommitAnimating>)animator API_AVAILABLE(ios(13.0))
|
|
{
|
|
NSString *roomId = [self.contextMenuProvider roomIdFrom:configuration.identifier];
|
|
|
|
if (!roomId)
|
|
{
|
|
self.recentsUpdateEnabled = YES;
|
|
return;
|
|
}
|
|
|
|
[animator addCompletion:^{
|
|
self.recentsUpdateEnabled = YES;
|
|
[self showRoomWithRoomId:roomId inMatrixSession:self.mainSession];
|
|
}];
|
|
}
|
|
|
|
- (UITargetedPreview *)tableView:(UITableView *)tableView previewForDismissingContextMenuWithConfiguration:(UIContextMenuConfiguration *)configuration API_AVAILABLE(ios(13.0))
|
|
{
|
|
self.recentsUpdateEnabled = YES;
|
|
return nil;
|
|
}
|
|
|
|
#pragma mark - RoomContextActionServiceDelegate
|
|
|
|
- (void)roomContextActionServiceDidJoinRoom:(id<RoomContextActionServiceProtocol>)service
|
|
{
|
|
[self showRoomWithRoomId:service.roomId inMatrixSession:service.session];
|
|
}
|
|
|
|
- (void)roomContextActionServiceDidLeaveRoom:(id<RoomContextActionServiceProtocol>)service
|
|
{
|
|
[self.userIndicatorStore presentSuccessWithLabel:VectorL10n.roomParticipantsLeaveSuccess];
|
|
}
|
|
|
|
- (void)roomContextActionService:(id<RoomContextActionServiceProtocol>)service presentAlert:(UIAlertController *)alertController
|
|
{
|
|
[self presentViewController:alertController animated:YES completion:nil];
|
|
}
|
|
|
|
- (void)roomContextActionService:(id<RoomContextActionServiceProtocol>)service updateActivityIndicator:(BOOL)isActive
|
|
{
|
|
if (isActive)
|
|
{
|
|
[self startActivityIndicator];
|
|
}
|
|
else if ([self canStopActivityIndicator])
|
|
{
|
|
[self stopActivityIndicator];
|
|
}
|
|
}
|
|
|
|
- (void)roomContextActionService:(id<RoomContextActionServiceProtocol>)service showRoomNotificationSettingsForRoomWithId:(NSString *)roomId
|
|
{
|
|
editedRoomId = roomId;
|
|
[self changeEditedRoomNotificationSettings];
|
|
editedRoomId = nil;
|
|
}
|
|
|
|
-(void)roomContextActionServiceDidMarkRoom:(id<RoomContextActionServiceProtocol>)service
|
|
{
|
|
[self refreshRecentsTable];
|
|
}
|
|
|
|
#pragma mark - RecentCellContextMenuProviderDelegate
|
|
|
|
- (void)recentCellContextMenuProviderDidStartShowingPreview:(RecentCellContextMenuProvider *)menuProvider
|
|
{
|
|
self.recentsUpdateEnabled = NO;
|
|
}
|
|
|
|
@end
|