From ea6d3db96d4cb26a5b84873fd6a8902bad67d7fc Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 28 Jul 2017 15:00:45 +0200 Subject: [PATCH 1/5] User directory: Use the new hs api (https://github.com/vector-im/riot-meta/issues/95) --- Riot/Assets/en.lproj/Vector.strings | 3 +- Riot/Model/Contact/ContactsDataSource.h | 19 ++++++ Riot/Model/Contact/ContactsDataSource.m | 84 +++++++++++++++++++++++-- 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 9091840e6..3fbf71646 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -167,7 +167,8 @@ "contacts_address_book_no_contact" = "No local contacts"; "contacts_address_book_permission_required" = "Permission required to access local contacts"; "contacts_address_book_permission_denied" = "You didn't allow Riot to access your local contacts"; -"contacts_matrix_users_section" = "KNOWN CONTACTS"; +"contacts_user_directory_section" = "USER DIRECTORY"; +"contacts_user_directory_offline_section" = "USER DIRECTORY (offline)"; // Chat participants "room_participants_title" = "Participants"; diff --git a/Riot/Model/Contact/ContactsDataSource.h b/Riot/Model/Contact/ContactsDataSource.h index b63f74514..4e8005f0c 100644 --- a/Riot/Model/Contact/ContactsDataSource.h +++ b/Riot/Model/Contact/ContactsDataSource.h @@ -16,6 +16,20 @@ #import +/** + The state of the users search from the homeserver user directory. + */ +typedef enum : NSUInteger +{ + ContactsDataSourceUserDirectoryStateLoading, + ContactsDataSourceUserDirectoryStateLoadedButLimited, + ContactsDataSourceUserDirectoryStateLoaded, + // The search is based on local known matrix contacts + ContactsDataSourceUserDirectoryStateOfflineLoading, + ContactsDataSourceUserDirectoryStateOfflineLoaded +} ContactsDataSourceUserDirectoryState; + + /** 'ContactsDataSource' is a base class to handle contacts in Riot. */ @@ -145,4 +159,9 @@ */ @property (nonatomic, readonly) MXKContact *searchInputContact; +/** + The state of the users search from the homeserver user directory. + */ +@property (nonatomic, readonly) ContactsDataSourceUserDirectoryState userDirectoryState; + @end diff --git a/Riot/Model/Contact/ContactsDataSource.m b/Riot/Model/Contact/ContactsDataSource.m index 5c880b241..bab141466 100644 --- a/Riot/Model/Contact/ContactsDataSource.m +++ b/Riot/Model/Contact/ContactsDataSource.m @@ -33,6 +33,9 @@ NSString *searchProcessingText; NSMutableArray *searchProcessingLocalContacts; NSMutableArray *searchProcessingMatrixContacts; + + // The current request to the homeserver user directory + MXHTTPOperation *hsUserDirectoryOperation; BOOL forceSearchResultRefresh; @@ -115,6 +118,9 @@ localContactsCheckboxContainer = nil; localContactsCheckbox = nil; + + [hsUserDirectoryOperation cancel]; + hsUserDirectoryOperation = nil; [super destroy]; } @@ -145,6 +151,13 @@ } - (void)searchWithPattern:(NSString *)searchText forceReset:(BOOL)forceRefresh +{ + // If possible, always start a new search by asking the homeserver user directory + BOOL hsUserDirectory = (self.mxSession.state != MXSessionStateHomeserverNotReachable); + [self searchWithPattern:searchText forceReset:forceRefresh hsUserDirectory:hsUserDirectory]; +} + +- (void)searchWithPattern:(NSString *)searchText forceReset:(BOOL)forceRefresh hsUserDirectory:(BOOL)hsUserDirectory { // Update search results. searchText = [searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; @@ -161,11 +174,59 @@ shrinkedSectionsBitMask = 0; } } - else if (forceRefresh || !searchProcessingText.length || [searchText hasPrefix:searchProcessingText] == NO) + else if (forceRefresh || ![searchText isEqualToString:searchProcessingText]) { // Prepare on the main thread the arrays used to initialize the search on the processing queue. unfilteredLocalContacts = [self unfilteredLocalContactsArray]; - unfilteredMatrixContacts = [self unfilteredMatrixContactsArray]; + if (!hsUserDirectory) + { + _userDirectoryState = ContactsDataSourceUserDirectoryStateOfflineLoading; + unfilteredMatrixContacts = [self unfilteredMatrixContactsArray]; + } + else if (![searchText isEqualToString:searchProcessingText]) + { + _userDirectoryState = ContactsDataSourceUserDirectoryStateLoading; + + // Make a search on the homeserver user directory + [filteredMatrixContacts removeAllObjects]; + filteredMatrixContacts = nil; + + // Cancel previous operation + if (hsUserDirectoryOperation) + { + [hsUserDirectoryOperation cancel]; + hsUserDirectoryOperation = nil; + } + + hsUserDirectoryOperation = [self.mxSession.matrixRestClient searchUsers:searchText limit:100 success:^(MXUserSearchResponse *userSearchResponse) { + + filteredMatrixContacts = [NSMutableArray arrayWithCapacity:userSearchResponse.results.count]; + + // Keep the response order as the hs ordered users by relevance + for (MXUser *mxUser in userSearchResponse.results) + { + MXKContact *contact = [[MXKContact alloc] initMatrixContactWithDisplayName:mxUser.displayname andMatrixID:mxUser.userId]; + [filteredMatrixContacts addObject:contact]; + } + + hsUserDirectoryOperation = nil; + + _userDirectoryState = userSearchResponse.limited ? ContactsDataSourceUserDirectoryStateLoadedButLimited : ContactsDataSourceUserDirectoryStateLoaded; + + // And inform the delegate about the update + [self.delegate dataSource:self didCellChange:nil]; + + } failure:^(NSError *error) { + + // Ignore connection cancellation error + if ((![error.domain isEqualToString:NSURLErrorDomain] || error.code != NSURLErrorCancelled)) + { + // But for other errors, launch a local search + NSLog(@"[ContactsDataSource] [MXRestClient searchUsers] returns an error. Do a search on local known contacts"); + [self searchWithPattern:searchText forceReset:forceRefresh hsUserDirectory:NO]; + } + }]; + } // Disclose the sections shrinkedSectionsBitMask = 0; @@ -239,7 +300,12 @@ // Update the filtered contacts. currentSearchText = searchProcessingText; filteredLocalContacts = searchProcessingLocalContacts; - filteredMatrixContacts = searchProcessingMatrixContacts; + + if (!hsUserDirectory) + { + filteredMatrixContacts = searchProcessingMatrixContacts; + _userDirectoryState = ContactsDataSourceUserDirectoryStateOfflineLoaded; + } if (!self.forceMatrixIdInDisplayName) { @@ -643,7 +709,17 @@ } else //if (section == filteredMatrixContactsSection) { - title = NSLocalizedStringFromTable(@"contacts_matrix_users_section", @"Vector", nil); + switch (_userDirectoryState) + { + case ContactsDataSourceUserDirectoryStateOfflineLoading: + case ContactsDataSourceUserDirectoryStateOfflineLoaded: + title = NSLocalizedStringFromTable(@"contacts_user_directory_offline_section", @"Vector", nil); + break; + + default: + title = NSLocalizedStringFromTable(@"contacts_user_directory_section", @"Vector", nil); + break; + } if (currentSearchText.length) { From 62f612d5bb1ee3f6bc83ef3d29dd2e16fcf74b6a Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 28 Jul 2017 15:02:11 +0200 Subject: [PATCH 2/5] User directory: Make sure a valid MXSession is provided to ContactsDataSource https://github.com/vector-im/riot-meta/issues/95 --- Riot/ViewController/PeopleViewController.m | 12 ++++++---- .../RoomParticipantsViewController.m | 2 +- Riot/ViewController/RoomViewController.m | 2 +- Riot/ViewController/StartChatViewController.m | 24 +++++++++---------- .../UnifiedSearchViewController.m | 2 +- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Riot/ViewController/PeopleViewController.m b/Riot/ViewController/PeopleViewController.m index 52ba8fb49..9dfa3502b 100644 --- a/Riot/ViewController/PeopleViewController.m +++ b/Riot/ViewController/PeopleViewController.m @@ -50,11 +50,6 @@ contactsSectionNumber = 0; self.screenName = @"People"; - - // Prepare its contacts data source - contactsDataSource = [[ContactsDataSource alloc] init]; - contactsDataSource.contactCellAccessoryType = UITableViewCellAccessoryDisclosureIndicator; - contactsDataSource.delegate = self; } - (void)viewDidLoad @@ -151,6 +146,13 @@ recentsDataSource = (RecentsDataSource*)listDataSource; } + if (!contactsDataSource) + { + // Prepare its contacts data source + contactsDataSource = [[ContactsDataSource alloc] initWithMatrixSession:listDataSource.mxSession]; + contactsDataSource.contactCellAccessoryType = UITableViewCellAccessoryDisclosureIndicator; + contactsDataSource.delegate = self; + } } #pragma mark - MXKDataSourceDelegate diff --git a/Riot/ViewController/RoomParticipantsViewController.m b/Riot/ViewController/RoomParticipantsViewController.m index 88ebc5d00..843d3fbb4 100644 --- a/Riot/ViewController/RoomParticipantsViewController.m +++ b/Riot/ViewController/RoomParticipantsViewController.m @@ -615,7 +615,7 @@ contactsPickerViewController.contactsTableViewControllerDelegate = self; // Prepare its data source - ContactsDataSource *contactsDataSource = [[ContactsDataSource alloc] init]; + ContactsDataSource *contactsDataSource = [[ContactsDataSource alloc] initWithMatrixSession:self.mxRoom.mxSession]; contactsDataSource.areSectionsShrinkable = YES; contactsDataSource.displaySearchInputInContactsList = YES; contactsDataSource.forceMatrixIdInDisplayName = YES; diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index c8aed8166..cd1c3fa0e 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -2576,7 +2576,7 @@ contactsPickerViewController.contactsTableViewControllerDelegate = self; // Prepare its data source - ContactsDataSource *contactsDataSource = [[ContactsDataSource alloc] init]; + ContactsDataSource *contactsDataSource = [[ContactsDataSource alloc] initWithMatrixSession:self.roomDataSource.mxSession]; contactsDataSource.areSectionsShrinkable = YES; contactsDataSource.displaySearchInputInContactsList = YES; contactsDataSource.forceMatrixIdInDisplayName = YES; diff --git a/Riot/ViewController/StartChatViewController.m b/Riot/ViewController/StartChatViewController.m index 47f82c85d..ca31abd3d 100644 --- a/Riot/ViewController/StartChatViewController.m +++ b/Riot/ViewController/StartChatViewController.m @@ -74,17 +74,6 @@ // Assign itself as delegate self.contactsTableViewControllerDelegate = self; - - // Prepare its data source - ContactsDataSource *dataSource = [[ContactsDataSource alloc] init]; - dataSource.areSectionsShrinkable = YES; - dataSource.displaySearchInputInContactsList = YES; - dataSource.forceMatrixIdInDisplayName = YES; - // Add a plus icon to the contact cell when a search session is in progress, - // in order to make it more understandable for the end user. - dataSource.contactCellAccessoryImage = [UIImage imageNamed:@"plus_icon"]; - - [self displayList:dataSource]; } - (void)viewDidLoad @@ -121,7 +110,18 @@ { [self addMatrixSession:mxSession]; } - + + // Prepare its data source + ContactsDataSource *dataSource = [[ContactsDataSource alloc] initWithMatrixSession:self.mainSession]; // TO TEST + dataSource.areSectionsShrinkable = YES; + dataSource.displaySearchInputInContactsList = YES; + dataSource.forceMatrixIdInDisplayName = YES; + // Add a plus icon to the contact cell when a search session is in progress, + // in order to make it more understandable for the end user. + dataSource.contactCellAccessoryImage = [UIImage imageNamed:@"plus_icon"]; + + [self displayList:dataSource]; + cancelBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(onButtonPressed:)]; self.navigationItem.leftBarButtonItem = cancelBarButtonItem; diff --git a/Riot/ViewController/UnifiedSearchViewController.m b/Riot/ViewController/UnifiedSearchViewController.m index ca992808d..9947fff5c 100644 --- a/Riot/ViewController/UnifiedSearchViewController.m +++ b/Riot/ViewController/UnifiedSearchViewController.m @@ -211,7 +211,7 @@ [filesSearchViewController displaySearch:filesSearchDataSource]; // Init the search for people - peopleSearchDataSource = [[ContactsDataSource alloc] init]; + peopleSearchDataSource = [[ContactsDataSource alloc] initWithMatrixSession:mainSession]; peopleSearchDataSource.areSectionsShrinkable = YES; peopleSearchDataSource.displaySearchInputInContactsList = YES; peopleSearchDataSource.contactCellAccessoryType = UITableViewCellAccessoryDisclosureIndicator; From d86ceeb012ba18a2d684fd62d6f710938e385c19 Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 28 Jul 2017 15:04:28 +0200 Subject: [PATCH 3/5] User directory: kill all strings referring to known contacts --- Riot/Assets/de.lproj/Vector.strings | 1 - Riot/Assets/fr.lproj/Vector.strings | 1 - Riot/Assets/nl.lproj/Vector.strings | 1 - Riot/Model/Contact/ContactsDataSource.m | 8 ++++---- Riot/ViewController/ContactsTableViewController.m | 2 +- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 3949af8a0..1b4e0bddb 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -114,7 +114,6 @@ "contacts_address_book_matrix_users_toggle" = "Nur Matrix-Nutzer"; "contacts_address_book_no_contact" = "Keine lokalen Kontakte"; "contacts_address_book_permission_required" = "Berechtigungen benötigt um auf lokale Kontakte zuzugreifen"; -"contacts_matrix_users_section" = "BEKANNTE KONTAKTE"; // Chat participants "room_participants_title" = "Teilnehmer"; "room_participants_add_participant" = "Teilnehmer hinzufügen"; diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 5cd7f3e57..4197f2a64 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -131,7 +131,6 @@ "contacts_address_book_no_contact" = "Aucun contact local"; "contacts_address_book_permission_required" = "Permissions requises pour accéder aux contacts locaux"; "contacts_address_book_permission_denied" = "Vous n'avez pas autorisé Riot à accéder à vos contacts locaux"; -"contacts_matrix_users_section" = "CONTACTS CONNUS"; // Chat participants "room_participants_title" = "Membres"; "room_participants_add_participant" = "Ajouter un membre"; diff --git a/Riot/Assets/nl.lproj/Vector.strings b/Riot/Assets/nl.lproj/Vector.strings index 3f6001a41..06c777f1f 100644 --- a/Riot/Assets/nl.lproj/Vector.strings +++ b/Riot/Assets/nl.lproj/Vector.strings @@ -161,7 +161,6 @@ "contacts_address_book_no_contact" = "Geen lokale contacten"; "contacts_address_book_permission_required" = "Permissie vereist voor toegang tot de lokale contacten"; "contacts_address_book_permission_denied" = "Je hebt Riot geen toegang tot je lokale contacten toegestaan"; -"contacts_matrix_users_section" = "BEKENDE CONTACTEN"; // Chat participants "room_participants_title" = "Deelnemers"; diff --git a/Riot/Model/Contact/ContactsDataSource.m b/Riot/Model/Contact/ContactsDataSource.m index bab141466..a870a440b 100644 --- a/Riot/Model/Contact/ContactsDataSource.m +++ b/Riot/Model/Contact/ContactsDataSource.m @@ -20,7 +20,7 @@ #import "RiotDesignValues.h" #define CONTACTSDATASOURCE_LOCALCONTACTS_BITWISE 0x01 -#define CONTACTSDATASOURCE_KNOWNCONTACTS_BITWISE 0x02 +#define CONTACTSDATASOURCE_USERDIRECTORY_BITWISE 0x02 #define CONTACTSDATASOURCE_DEFAULT_SECTION_HEADER_HEIGHT 30.0 #define CONTACTSDATASOURCE_LOCALCONTACTS_SECTION_HEADER_HEIGHT 65.0 @@ -501,7 +501,7 @@ // Display a default cell when no local contacts is available. count = filteredLocalContacts.count ? filteredLocalContacts.count : 1; } - else if (section == filteredMatrixContactsSection && !(shrinkedSectionsBitMask & CONTACTSDATASOURCE_KNOWNCONTACTS_BITWISE)) + else if (section == filteredMatrixContactsSection && !(shrinkedSectionsBitMask & CONTACTSDATASOURCE_USERDIRECTORY_BITWISE)) { // Display a default cell when no contacts is available. count = filteredMatrixContacts.count ? filteredMatrixContacts.count : 1; @@ -781,7 +781,7 @@ // This section is collapsable only if it is not empty if (filteredMatrixContacts.count) { - sectionBitwise = CONTACTSDATASOURCE_KNOWNCONTACTS_BITWISE; + sectionBitwise = CONTACTSDATASOURCE_USERDIRECTORY_BITWISE; } } } @@ -1004,7 +1004,7 @@ { // Return the section header used when the section is shrinked NSInteger savedShrinkedSectionsBitMask = shrinkedSectionsBitMask; - shrinkedSectionsBitMask = CONTACTSDATASOURCE_LOCALCONTACTS_BITWISE | CONTACTSDATASOURCE_KNOWNCONTACTS_BITWISE; + shrinkedSectionsBitMask = CONTACTSDATASOURCE_LOCALCONTACTS_BITWISE | CONTACTSDATASOURCE_USERDIRECTORY_BITWISE; UIView *stickyHeader = [self viewForHeaderInSection:section withFrame:frame]; diff --git a/Riot/ViewController/ContactsTableViewController.m b/Riot/ViewController/ContactsTableViewController.m index 921094c8e..352f83ac9 100644 --- a/Riot/ViewController/ContactsTableViewController.m +++ b/Riot/ViewController/ContactsTableViewController.m @@ -22,7 +22,7 @@ #import "AppDelegate.h" #define CONTACTS_TABLEVC_LOCALCONTACTS_BITWISE 0x01 -#define CONTACTS_TABLEVC_KNOWNCONTACTS_BITWISE 0x02 +#define CONTACTS_TABLEVC_USERDIRECTORY_BITWISE 0x02 #define CONTACTS_TABLEVC_DEFAULT_SECTION_HEADER_HEIGHT 30.0 #define CONTACTS_TABLEVC_LOCALCONTACTS_SECTION_HEADER_HEIGHT 65.0 From 6a76efcbcb02bc3646492add9d48786e80f1da1b Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 28 Jul 2017 15:16:25 +0200 Subject: [PATCH 4/5] User directory: Display "> nn" when the search results is limited. Reduce the requested number of users from 100 to 50 (but the hs still returns **51** users...) --- Riot/Model/Contact/ContactsDataSource.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Riot/Model/Contact/ContactsDataSource.m b/Riot/Model/Contact/ContactsDataSource.m index a870a440b..9ed32d30a 100644 --- a/Riot/Model/Contact/ContactsDataSource.m +++ b/Riot/Model/Contact/ContactsDataSource.m @@ -198,7 +198,7 @@ hsUserDirectoryOperation = nil; } - hsUserDirectoryOperation = [self.mxSession.matrixRestClient searchUsers:searchText limit:100 success:^(MXUserSearchResponse *userSearchResponse) { + hsUserDirectoryOperation = [self.mxSession.matrixRestClient searchUsers:searchText limit:50 success:^(MXUserSearchResponse *userSearchResponse) { filteredMatrixContacts = [NSMutableArray arrayWithCapacity:userSearchResponse.results.count]; @@ -729,7 +729,8 @@ if (count) { - NSString *roomCount = [NSString stringWithFormat:@" %tu", count]; + NSString *roomCountFormat = (_userDirectoryState == ContactsDataSourceUserDirectoryStateLoadedButLimited) ? @" > %tu" : @" %tu"; + NSString *roomCount = [NSString stringWithFormat:roomCountFormat, count]; NSMutableAttributedString *mutableSectionTitle = [[NSMutableAttributedString alloc] initWithString:title attributes:@{NSForegroundColorAttributeName : kRiotTextColorBlack, From c00e6ecce3bbc930846691674e177a15958455ae Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 28 Jul 2017 15:24:25 +0200 Subject: [PATCH 5/5] User directory: Display "Search in progress..." instead of "No results" when we are searching --- Riot/Model/Contact/ContactsDataSource.m | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Riot/Model/Contact/ContactsDataSource.m b/Riot/Model/Contact/ContactsDataSource.m index 9ed32d30a..c5980080e 100644 --- a/Riot/Model/Contact/ContactsDataSource.m +++ b/Riot/Model/Contact/ContactsDataSource.m @@ -600,7 +600,15 @@ // Check whether a search session is in progress if (currentSearchText.length) { - tableViewCell.textLabel.text = NSLocalizedStringFromTable(@"search_no_result", @"Vector", nil); + if (indexPath.section == filteredMatrixContactsSection && + (_userDirectoryState == ContactsDataSourceUserDirectoryStateLoading || _userDirectoryState == ContactsDataSourceUserDirectoryStateOfflineLoading)) + { + tableViewCell.textLabel.text = [NSBundle mxk_localizedStringForKey:@"search_searching"]; + } + else + { + tableViewCell.textLabel.text = NSLocalizedStringFromTable(@"search_no_result", @"Vector", nil); + } } else if (indexPath.section == filteredLocalContactsSection) {