diff --git a/AUTHORS.rst b/AUTHORS.rst index 20c98179f..adfe87b20 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -19,4 +19,7 @@ Aaron Raimist * Update the services supported by the app. Denis Morozov - * Fix default room avatar for an empty room #1044. \ No newline at end of file + * Fix default room avatar for an empty room #1044 + * PR #1090, PR #1113, PR #1123: UX improvements + * PR #1132: Check email validity during reset password operation + \ No newline at end of file diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index f66690fef..04e01d3bc 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -134,6 +134,7 @@ "directory_cell_description" = "%tu rooms"; "directory_search_results_title" = "Browse directory results"; "directory_search_results" = "%tu results found for %@"; +"directory_search_results_more_than" = ">%tu results found for %@"; "directory_searching_title" = "Searching directory..."; "directory_search_fail" = "Failed to fetch data"; diff --git a/Riot/Model/RoomList/PublicRoomsDirectoryDataSource.h b/Riot/Model/RoomList/PublicRoomsDirectoryDataSource.h index 266cc80bc..62aa79ada 100644 --- a/Riot/Model/RoomList/PublicRoomsDirectoryDataSource.h +++ b/Riot/Model/RoomList/PublicRoomsDirectoryDataSource.h @@ -1,5 +1,6 @@ /* Copyright 2015 OpenMarket Ltd + Copyright 2017 Vector Creations Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -33,27 +34,50 @@ @interface PublicRoomsDirectoryDataSource : MXKDataSource /** - All public rooms of the directory. + The number of public rooms matching `searchPattern`. + It is accurate only if 'moreThanRoomsCount' is NO. */ -@property (nonatomic, readonly) NSArray *rooms; +@property (nonatomic, readonly) NSUInteger roomsCount; /** - The filter being applied. Nil if there is no filter. - A 'AND' search is made with the strings of the array. - Setting a new value may trigger a request to the home server. So, the data source state + In case of search with a lot of matching public rooms, we cannot return an accurate + value except by paginating the full list of rooms, which is not expected. + + This flag indicates that we know that there is more matching rooms than we got + so far. + */ +@property (nonatomic, readonly) BOOL moreThanRoomsCount; + +/** + The maximum number of public rooms to retrieve during a pagination. + Default is 20. + */ +@property (nonatomic) NSUInteger paginationLimit; + +/** + The flag indicating that all rooms has been retrieved from the homeserver. + */ +@property (nonatomic, readonly) BOOL hasReachedPaginationEnd; + +/** + The filter being applied. + + Nil if there is no filter; the data source will get all public rooms. + Default is nil. + + Setting a new value may trigger a request to the homeserver. So, the data source state may change to MXKDataSourceStatePreparing. */ -@property (nonatomic) NSArray *searchPatternsList; +@property (nonatomic) NSString *searchPattern; /** - Public rooms of the directory that match `searchPatternsList`. + Paginate more public rooms matching `from the homeserver. + + @param success A block object called when the operation succeeds. It provides the number of got rooms. + @param failure A block object called when the operation fails. */ -@property (nonatomic, readonly) NSArray *filteredRooms; - -/** - Refresh public rooms list (take into account the potential search pattern list). - */ -- (void)refreshPublicRooms; +- (MXHTTPOperation*)paginate:(void (^)(NSUInteger roomsAdded))success + failure:(void (^)(NSError *error))failure; /** Get the index path of the cell related to the provided roomId and session. @@ -64,4 +88,12 @@ */ - (NSIndexPath*)cellIndexPathWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)mxSession; +/** + Get the public at the given index path. + + @param indexPath the position of the room in the table view. + @return the public room object. + */ +- (MXPublicRoom*)roomAtIndexPath:(NSIndexPath*)indexPath; + @end diff --git a/Riot/Model/RoomList/PublicRoomsDirectoryDataSource.m b/Riot/Model/RoomList/PublicRoomsDirectoryDataSource.m index ae2d05a75..15a475649 100644 --- a/Riot/Model/RoomList/PublicRoomsDirectoryDataSource.m +++ b/Riot/Model/RoomList/PublicRoomsDirectoryDataSource.m @@ -1,5 +1,6 @@ /* Copyright 2015 OpenMarket Ltd + Copyright 2017 Vector Creations Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -32,23 +33,51 @@ double const kPublicRoomsDirectoryDataExpiration = 10; // The pending request to refresh public rooms data. MXHTTPOperation *publicRoomsRequest; - // The date of the last fetched data. - NSDate *lastRefreshDate; + /** + All public rooms fetched so far. + */ + NSMutableArray *rooms; + + /** + The next token to use for pagination. + */ + NSString *nextBatch; } @end @implementation PublicRoomsDirectoryDataSource -- (void)setSearchPatternsList:(NSArray *)newSearchPatternsList +- (instancetype)init { - NSString *searchPatternsListString = [_searchPatternsList componentsJoinedByString:@""]; - NSString *newSearchPatternsListString = [newSearchPatternsList componentsJoinedByString:@""]; - - if ((searchPatternsListString || newSearchPatternsListString) && ![newSearchPatternsListString isEqualToString:searchPatternsListString]) + self = [super init]; + if (self) { - _searchPatternsList = newSearchPatternsList; - [self refreshPublicRooms]; + rooms = [NSMutableArray array]; + _paginationLimit = 20; + } + return self; +} + +- (void)setSearchPattern:(NSString *)searchPattern +{ + if (searchPattern) + { + if (![searchPattern isEqualToString:_searchPattern]) + { + _searchPattern = searchPattern; + [self startPagination]; + } + } + else + { + // Refresh if the previous search was not nil + // or if it is the first time we make a search + if (_searchPattern || rooms.count == 0) + { + _searchPattern = searchPattern; + [self startPagination]; + } } } @@ -57,9 +86,9 @@ double const kPublicRoomsDirectoryDataExpiration = 10; NSIndexPath *indexPath = nil; // Look for the public room - for (NSInteger index = 0; index < _filteredRooms.count; index ++) + for (NSInteger index = 0; index < rooms.count; index ++) { - MXPublicRoom *room = _filteredRooms[index]; + MXPublicRoom *room = rooms[index]; if ([roomId isEqualToString:room.roomId]) { // Got it @@ -71,118 +100,122 @@ double const kPublicRoomsDirectoryDataExpiration = 10; return indexPath; } -- (void)refreshPublicRooms +- (MXPublicRoom *)roomAtIndexPath:(NSIndexPath *)indexPath { - // Do not refresh data if it is not too old - if (lastRefreshDate && -lastRefreshDate.timeIntervalSinceNow < kPublicRoomsDirectoryDataExpiration) + MXPublicRoom *room; + + if (indexPath.row < rooms.count) { - // Do not disturb the current request if any - if (!publicRoomsRequest) - { - // Apply the new filter on the current data - [self refreshFilteredPublicRooms]; - - [self setState:MXKDataSourceStateReady]; - } + room = rooms[indexPath.row]; } - else - { - // Cancel the previous request - if (publicRoomsRequest) - { - [publicRoomsRequest cancel]; - } - [self setState:MXKDataSourceStatePreparing]; - - lastRefreshDate = [NSDate date]; - - // Get the public rooms from the server - publicRoomsRequest = [self.mxSession.matrixRestClient publicRooms:^(NSArray *rooms) { - - // Order rooms by their members count - _rooms = [rooms sortedArrayUsingComparator:^NSComparisonResult(id a, id b) - { - MXPublicRoom *firstRoom = (MXPublicRoom*)a; - MXPublicRoom *secondRoom = (MXPublicRoom*)b; - - // Compare member count - if (firstRoom.numJoinedMembers < secondRoom.numJoinedMembers) - { - return NSOrderedDescending; - } - else if (firstRoom.numJoinedMembers > secondRoom.numJoinedMembers) - { - return NSOrderedAscending; - } - else - { - // Alphabetic order - return [firstRoom.displayname compare:secondRoom.displayname options:NSCaseInsensitiveSearch]; - } - }]; - - lastRefreshDate = [NSDate date]; - publicRoomsRequest = nil; - - [self refreshFilteredPublicRooms]; - - [self setState:MXKDataSourceStateReady]; - - } failure:^(NSError *error) { - - NSLog(@"[PublicRoomsDirectoryDataSource] Failed to fecth public rooms."); - - [self setState:MXKDataSourceStateFailed]; - - // Reset the refresh date so that the user can retry the request by changing the search text input content - lastRefreshDate = nil; - - // Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; - } + return room; } -#pragma mark - Private methods - -- (void)refreshFilteredPublicRooms +- (void)startPagination { - // Apply filter if any - if (_searchPatternsList) + // Cancel the previous request + if (publicRoomsRequest) { - NSMutableArray *filteredRooms = [NSMutableArray array]; - for (MXPublicRoom *publicRoom in _rooms) - { - if ([filteredRooms indexOfObjectIdenticalTo:publicRoom] == NSNotFound) - { - // Do a AND search - BOOL matchAll = YES; - for (NSString *pattern in _searchPatternsList) - { - if (pattern.length && NO == [publicRoom.displayname localizedCaseInsensitiveContainsString:pattern]) - { - matchAll = NO; - break; - } - } + [publicRoomsRequest cancel]; + } - if (matchAll) - { - [filteredRooms addObject:publicRoom]; - } + [self setState:MXKDataSourceStatePreparing]; + + // Reset all pagination vars + [rooms removeAllObjects]; + nextBatch = nil; + _roomsCount = 0; + _moreThanRoomsCount = NO; + _hasReachedPaginationEnd = NO; + + // And do a single pagination + [self paginate:nil failure:nil]; +} + +- (MXHTTPOperation *)paginate:(void (^)(NSUInteger))complete failure:(void (^)(NSError *))failure +{ + if (_hasReachedPaginationEnd) + { + return nil; + } + + __weak typeof(self) weakSelf = self; + + // Get the public rooms from the server + MXHTTPOperation *newPublicRoomsRequest; + newPublicRoomsRequest = [self.mxSession.matrixRestClient publicRoomsOnServer:nil limit:_paginationLimit since:nextBatch filter:_searchPattern thirdPartyInstanceId:nil includeAllNetworks:NO success:^(MXPublicRoomsResponse *publicRoomsResponse) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->publicRoomsRequest = nil; + + [self->rooms addObjectsFromArray:publicRoomsResponse.chunk]; + self->nextBatch = publicRoomsResponse.nextBatch; + + if (!self->_searchPattern) + { + // When there is no search, we can use totalRoomCountEstimate returned by the server + self->_roomsCount = publicRoomsResponse.totalRoomCountEstimate; + self->_moreThanRoomsCount = NO; + } + else + { + // Else we can only display something like ">20 matching rooms" + self->_roomsCount = self->rooms.count; + self->_moreThanRoomsCount = publicRoomsResponse.nextBatch ? YES : NO; + } + + // Detect pagination end + if (!publicRoomsResponse.nextBatch) + { + _hasReachedPaginationEnd = YES; + } + + [self setState:MXKDataSourceStateReady]; + + if (complete) + { + complete(publicRoomsResponse.chunk.count); } } - _filteredRooms = filteredRooms; - } - else - { - _filteredRooms = _rooms; - } + } failure:^(NSError *error) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + if (!newPublicRoomsRequest || newPublicRoomsRequest.isCancelled) + { + // Do not take into account error coming from a cancellation + return; + } + + self->publicRoomsRequest = nil; + + NSLog(@"[PublicRoomsDirectoryDataSource] Failed to fecth public rooms."); + + [self setState:MXKDataSourceStateFailed]; + + // Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + + if (failure) + { + failure(error); + } + } + }]; + + publicRoomsRequest = newPublicRoomsRequest; + + return publicRoomsRequest; } +#pragma mark - Private methods // Update the MXKDataSource state and the delegate - (void)setState:(MXKDataSourceState)newState @@ -198,7 +231,7 @@ double const kPublicRoomsDirectoryDataExpiration = 10; - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return _filteredRooms.count; + return rooms.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath @@ -210,7 +243,7 @@ double const kPublicRoomsDirectoryDataExpiration = 10; publicRoomCell = [[PublicRoomTableViewCell alloc] init]; } - [publicRoomCell render:_filteredRooms[indexPath.row] withMatrixSession:self.mxSession]; + [publicRoomCell render:rooms[indexPath.row] withMatrixSession:self.mxSession]; return publicRoomCell; } diff --git a/Riot/Model/RoomList/RecentsDataSource.m b/Riot/Model/RoomList/RecentsDataSource.m index 982097923..8e577cbf3 100644 --- a/Riot/Model/RoomList/RecentsDataSource.m +++ b/Riot/Model/RoomList/RecentsDataSource.m @@ -57,6 +57,9 @@ // The potential room id or alias typed in search input. NSString *roomIdOrAlias; + + // Timer to not refresh publicRoomsDirectoryDataSource on every keystroke. + NSTimer *publicRoomsTriggerTimer; } @end @@ -196,7 +199,8 @@ if (!_hidePublicRoomsDirectory) { - [self.publicRoomsDirectoryDataSource refreshPublicRooms]; + // Start by looking for all public rooms + self.publicRoomsDirectoryDataSource.searchPattern = nil; } [self refreshRoomsSectionsAndReload]; @@ -767,11 +771,27 @@ } } +- (IBAction)onPublicRoomsSearchPatternUpdate:(id)sender +{ + if (publicRoomsTriggerTimer) + { + NSString *searchPattern = publicRoomsTriggerTimer.userInfo; + + [publicRoomsTriggerTimer invalidate]; + publicRoomsTriggerTimer = nil; + + _publicRoomsDirectoryDataSource.searchPattern = searchPattern; + } +} + #pragma mark - Override MXKDataSource - (void)destroy { [super destroy]; + + [publicRoomsTriggerTimer invalidate]; + publicRoomsTriggerTimer = nil; } #pragma mark - Override MXKRecentsDataSource @@ -798,7 +818,12 @@ if (_publicRoomsDirectoryDataSource) { - _publicRoomsDirectoryDataSource.searchPatternsList = patternsList; + NSString *searchPattern = [patternsList componentsJoinedByString:@" "]; + + // Do not send a /publicRooms request for every keystroke + // Let user finish typing + [publicRoomsTriggerTimer invalidate]; + publicRoomsTriggerTimer = [NSTimer scheduledTimerWithTimeInterval:0.7 target:self selector:@selector(onPublicRoomsSearchPatternUpdate:) userInfo:searchPattern repeats:NO]; } } diff --git a/Riot/ViewController/DirectoryViewController.m b/Riot/ViewController/DirectoryViewController.m index a8a72d8a4..9ce2f22ad 100644 --- a/Riot/ViewController/DirectoryViewController.m +++ b/Riot/ViewController/DirectoryViewController.m @@ -31,6 +31,9 @@ // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. id kAppDelegateDidTapStatusBarNotificationObserver; + + // The animated view displayed at the table view bottom when paginating + UIView* footerSpinnerView; } @end @@ -125,7 +128,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - MXPublicRoom *publicRoom = dataSource.filteredRooms[indexPath.row]; + MXPublicRoom *publicRoom = [dataSource roomAtIndexPath:indexPath]; // Check whether the user has already joined the selected public room if ([dataSource.mxSession roomWithRoomId:publicRoom.roomId]) @@ -159,6 +162,15 @@ } } +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + // Trigger inconspicuous pagination when user scrolls down + if ((scrollView.contentSize.height - scrollView.contentOffset.y - scrollView.frame.size.height) < 300) + { + [self triggerPagination]; + } +} + #pragma mark - Private methods - (void)openRoomWithId:(NSString*)roomId inMatrixSession:(MXSession*)mxSession @@ -201,4 +213,73 @@ } } +- (void)triggerPagination +{ + if (dataSource.hasReachedPaginationEnd || footerSpinnerView) + { + // We got all public rooms or we are already paginating + // Do nothing + return; + } + + [self addSpinnerFooterView]; + + [dataSource paginate:^(NSUInteger roomsAdded) { + + if (roomsAdded) + { + // Notify the table view there are new items at its tail + NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:roomsAdded]; + + NSUInteger numberOfRowsBefore = [dataSource tableView:self.tableView numberOfRowsInSection:0]; + numberOfRowsBefore -= roomsAdded; + + for (NSUInteger i = 0; i < roomsAdded; i++) + { + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:(numberOfRowsBefore + i) inSection:0]; + [indexPaths addObject:indexPath]; + } + + [self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic]; + } + + [self removeSpinnerFooterView]; + + } failure:^(NSError *error) { + + [self removeSpinnerFooterView]; + }]; +} + +- (void)addSpinnerFooterView +{ + if (!footerSpinnerView) + { + UIActivityIndicatorView* spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; + spinner.transform = CGAffineTransformMakeScale(0.75f, 0.75f); + CGRect frame = spinner.frame; + frame.size.height = 80; // 80 * 0.75 = 60 + spinner.bounds = frame; + + spinner.color = [UIColor darkGrayColor]; + spinner.hidesWhenStopped = NO; + spinner.backgroundColor = [UIColor clearColor]; + [spinner startAnimating]; + + // No need to manage constraints here, iOS defines them + self.tableView.tableFooterView = footerSpinnerView = spinner; + } +} + +- (void)removeSpinnerFooterView +{ + if (footerSpinnerView) + { + footerSpinnerView = nil; + + // Hide line separators of empty cells + self.tableView.tableFooterView = [[UIView alloc] init];; + } +} + @end diff --git a/Riot/ViewController/HomeViewController.m b/Riot/ViewController/HomeViewController.m index 4bafedf0a..4c5255869 100644 --- a/Riot/ViewController/HomeViewController.m +++ b/Riot/ViewController/HomeViewController.m @@ -923,8 +923,6 @@ tableViewMaskLayer.hidden = NO; self.backgroundImageView.hidden = YES; - [recentsDataSource searchWithPatterns:nil]; - recentsDataSource.hideRecents = NO; recentsDataSource.hidePublicRoomsDirectory = YES; diff --git a/Riot/Views/Authentication/ForgotPasswordInputsView.m b/Riot/Views/Authentication/ForgotPasswordInputsView.m index f7daff792..2f3c60c77 100644 --- a/Riot/Views/Authentication/ForgotPasswordInputsView.m +++ b/Riot/Views/Authentication/ForgotPasswordInputsView.m @@ -17,25 +17,25 @@ #import "ForgotPasswordInputsView.h" +#import "MXHttpOperation.h" #import "RiotDesignValues.h" @interface ForgotPasswordInputsView () -{ - /** - The current email validation - */ - MXK3PID *submittedEmail; - - /** - The current set of parameters ready to use. - */ - NSDictionary *parameters; - - /** - The block called when the parameters are ready and the user confirms he has checked his email. - */ - void (^didPrepareParametersCallback)(NSDictionary *parameters); -} + +/** + The current email validation request operation + */ +@property (nonatomic, strong) MXHTTPOperation *mxCurrentOperation; + +/** + The current set of parameters ready to use. + */ +@property (nonatomic, strong) NSDictionary *parameters; + +/** + The block called when the parameters are ready and the user confirms he has checked his email. + */ +@property (nonatomic, copy) void (^didPrepareParametersCallback)(NSDictionary *parameters); @end @@ -74,10 +74,10 @@ { [super destroy]; - submittedEmail = nil; + self.mxCurrentOperation = nil; - parameters = nil; - didPrepareParametersCallback = nil; + self.parameters = nil; + self.didPrepareParametersCallback = nil; } -(void)layoutSubviews @@ -165,8 +165,8 @@ if (callback) { // Prepare here parameters dict by checking each required fields. - parameters = nil; - didPrepareParametersCallback = nil; + self.parameters = nil; + self.didPrepareParametersCallback = nil; // Check the validity of the parameters NSString *errorMsg = [self validateParameters]; @@ -197,42 +197,74 @@ if (restClient) { // Launch email validation - submittedEmail = [[MXK3PID alloc] initWithMedium:kMX3PIDMediumEmail andAddress:self.emailTextField.text]; + NSString *clientSecret = [MXTools generateSecret]; - [submittedEmail requestValidationTokenWithMatrixRestClient:restClient - nextLink:nil - success:^{ - - didPrepareParametersCallback = callback; - - NSURL *identServerURL = [NSURL URLWithString:restClient.identityServer]; - parameters = @{ - @"auth": @{@"threepid_creds": @{@"client_secret": submittedEmail.clientSecret, @"id_server": identServerURL.host, @"sid": submittedEmail.sid}, @"type": kMXLoginFlowTypeEmailIdentity}, - @"new_password": self.passWordTextField.text}; - - [self hideInputsContainer]; - - self.messageLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"auth_reset_password_email_validation_message", @"Vector", nil), self.emailTextField.text]; - - self.messageLabel.hidden = NO; - - [self.nextStepButton addTarget:self action:@selector(didCheckEmail:) forControlEvents:UIControlEventTouchUpInside]; - - self.nextStepButton.hidden = NO; - - } failure:^(NSError *error) { - - NSLog(@"[ForgotPasswordInputsView] Failed to request email token"); - - // Ignore connection cancellation error - if (([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled)) - { - return; - } - - callback(nil); - - }]; + __weak typeof(self) weakSelf = self; + [restClient forgetPasswordForEmail:self.emailTextField.text + clientSecret:clientSecret + sendAttempt:1 + success:^(NSString *sid) { + typeof(weakSelf) strongSelf = weakSelf; + if (strongSelf) { + strongSelf.didPrepareParametersCallback = callback; + + NSURL *identServerURL = [NSURL URLWithString:restClient.identityServer]; + strongSelf.parameters = @{ + @"auth": @{ + @"threepid_creds": @{ + @"client_secret": clientSecret, + @"id_server": identServerURL.host, + @"sid": sid + }, + @"type": kMXLoginFlowTypeEmailIdentity + }, + @"new_password": strongSelf.passWordTextField.text + }; + + [strongSelf hideInputsContainer]; + + strongSelf.messageLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"auth_reset_password_email_validation_message", @"Vector", nil), strongSelf.emailTextField.text]; + + strongSelf.messageLabel.hidden = NO; + + [strongSelf.nextStepButton addTarget:strongSelf + action:@selector(didCheckEmail:) + forControlEvents:UIControlEventTouchUpInside]; + + strongSelf.nextStepButton.hidden = NO; + } + } failure:^(NSError *error) { + NSLog(@"[ForgotPasswordInputsView] Failed to request email token"); + + // Ignore connection cancellation error + if (([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled)) + { + return; + } + + NSString *errorMessage; + if (error.userInfo[@"error"]) + errorMessage = error.userInfo[@"error"]; + else + errorMessage = error.localizedDescription; + + __strong typeof(weakSelf) strongSelf = weakSelf; + if (strongSelf) { + if (strongSelf->inputsAlert) + { + [strongSelf->inputsAlert dismiss:NO]; + } + + strongSelf->inputsAlert = [[MXKAlert alloc] initWithTitle:[NSBundle mxk_localizedStringForKey:@"error"] message:errorMessage style:MXKAlertStyleAlert]; + strongSelf->inputsAlert.cancelButtonIndex = [inputsAlert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert) { + strongSelf->inputsAlert = nil; + if (strongSelf.delegate && [strongSelf.delegate respondsToSelector:@selector(authInputsViewDidCancelOperation:)]) + [strongSelf.delegate authInputsViewDidCancelOperation:strongSelf]; + }]; + + [strongSelf.delegate authInputsView:strongSelf presentMXKAlert:strongSelf->inputsAlert]; + } + }]; // Async response return; @@ -243,7 +275,7 @@ } } - callback(parameters); + callback(self.parameters); } } @@ -270,8 +302,8 @@ - (void)nextStep { // Here the password has been reseted with success - didPrepareParametersCallback = nil; - parameters = nil; + self.didPrepareParametersCallback = nil; + self.parameters = nil; [self hideInputsContainer]; @@ -284,15 +316,12 @@ - (void)reset { - // Cancel email validation if any - if (submittedEmail) - { - [submittedEmail cancelCurrentRequest]; - submittedEmail = nil; - } + // Cancel email validation request + [self.mxCurrentOperation cancel]; + self.mxCurrentOperation = nil; - parameters = nil; - didPrepareParametersCallback = nil; + self.parameters = nil; + self.didPrepareParametersCallback = nil; // Reset UI by hidding all items [self hideInputsContainer]; @@ -313,9 +342,9 @@ { if (sender == self.nextStepButton) { - if (didPrepareParametersCallback) + if (self.didPrepareParametersCallback) { - didPrepareParametersCallback(parameters); + self.didPrepareParametersCallback(self.parameters); } } } diff --git a/Riot/Views/RoomList/DirectoryRecentTableViewCell.m b/Riot/Views/RoomList/DirectoryRecentTableViewCell.m index 33d89a893..7818293a0 100644 --- a/Riot/Views/RoomList/DirectoryRecentTableViewCell.m +++ b/Riot/Views/RoomList/DirectoryRecentTableViewCell.m @@ -48,25 +48,25 @@ case MXKDataSourceStateReady: { - if (publicRoomsDirectoryDataSource.searchPatternsList) + if (publicRoomsDirectoryDataSource.searchPattern) { - // Concatenate all patterns into one string - NSString *filter = [publicRoomsDirectoryDataSource.searchPatternsList componentsJoinedByString:@" "]; - self.titleLabel.text = NSLocalizedStringFromTable(@"directory_search_results_title", @"Vector", nil); - self.descriptionLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"directory_search_results", @"Vector", nil), - publicRoomsDirectoryDataSource.filteredRooms.count, - filter]; + + // Do we need to display like ">20 results found" or "18 results found"? + NSString *descriptionLabel = publicRoomsDirectoryDataSource.moreThanRoomsCount ? NSLocalizedStringFromTable(@"directory_search_results_more_than", @"Vector", nil) : NSLocalizedStringFromTable(@"directory_search_results", @"Vector", nil); + + self.descriptionLabel.text = [NSString stringWithFormat:descriptionLabel, + publicRoomsDirectoryDataSource.roomsCount, + publicRoomsDirectoryDataSource.searchPattern]; } else { self.titleLabel.text = NSLocalizedStringFromTable(@"directory_cell_title", @"Vector", nil); self.descriptionLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"directory_cell_description", @"Vector", nil), - publicRoomsDirectoryDataSource.rooms.count]; + publicRoomsDirectoryDataSource.roomsCount]; } - - if (publicRoomsDirectoryDataSource.filteredRooms.count) + if (publicRoomsDirectoryDataSource.roomsCount) { self.userInteractionEnabled = YES; self.chevronImageView.hidden = NO;