Merge pull request #926 from vector-im/local_contacts_methods

Improve the people invite screens
This commit is contained in:
giomfo 2017-01-13 12:11:35 +01:00 committed by GitHub
commit 6af7f9022e
17 changed files with 1325 additions and 1552 deletions

View file

@ -207,6 +207,11 @@
F03BF6DB1D8BF5B1002EF6A7 /* voice_call_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F03BF6471D8BF5B1002EF6A7 /* voice_call_icon@2x.png */; };
F03BF6DC1D8BF5B1002EF6A7 /* voice_call_icon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = F03BF6481D8BF5B1002EF6A7 /* voice_call_icon@3x.png */; };
F03DE2A51D0EFA6A00E8B65C /* AttachmentsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F03DE2A41D0EFA6A00E8B65C /* AttachmentsViewController.m */; };
F046528D1E250B0A00EA4E77 /* ContactsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F046528C1E250B0A00EA4E77 /* ContactsTableViewController.m */; };
F046528F1E28439E00EA4E77 /* ContactsTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F046528E1E28439E00EA4E77 /* ContactsTableViewController.xib */; };
F04652931E28E0E300EA4E77 /* add_participant.png in Resources */ = {isa = PBXBuildFile; fileRef = F04652901E28E0E300EA4E77 /* add_participant.png */; };
F04652941E28E0E300EA4E77 /* add_participant@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F04652911E28E0E300EA4E77 /* add_participant@2x.png */; };
F04652951E28E0E300EA4E77 /* add_participant@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = F04652921E28E0E300EA4E77 /* add_participant@3x.png */; };
F047DBB51C576F2200952DA2 /* AuthenticationViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F047DBB41C576F2200952DA2 /* AuthenticationViewController.xib */; };
F047DBB91C576F6600952DA2 /* AuthInputsView.m in Sources */ = {isa = PBXBuildFile; fileRef = F047DBB71C576F6600952DA2 /* AuthInputsView.m */; };
F047DBBA1C576F6600952DA2 /* AuthInputsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F047DBB81C576F6600952DA2 /* AuthInputsView.xib */; };
@ -384,8 +389,6 @@
F0D2D9881C197DCB007B8C96 /* RoomIncomingTextMsgBubbleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F0D2D97F1C197DCB007B8C96 /* RoomIncomingTextMsgBubbleCell.xib */; };
F0D2D9891C197DCB007B8C96 /* RoomIncomingTextMsgWithoutSenderInfoBubbleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = F0D2D9811C197DCB007B8C96 /* RoomIncomingTextMsgWithoutSenderInfoBubbleCell.m */; };
F0D2D98A1C197DCB007B8C96 /* RoomIncomingTextMsgWithoutSenderInfoBubbleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F0D2D9821C197DCB007B8C96 /* RoomIncomingTextMsgWithoutSenderInfoBubbleCell.xib */; };
F0DD2C701D1308E800654345 /* ContactPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F0DD2C6F1D1308E800654345 /* ContactPickerViewController.m */; };
F0DD2C721D141B0600654345 /* ContactPickerViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F0DD2C711D141B0600654345 /* ContactPickerViewController.xib */; };
F0DD2C7B1D18386300654345 /* ContactDetailsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F0DD2C791D18386300654345 /* ContactDetailsViewController.m */; };
F0DD2C7C1D18386300654345 /* ContactDetailsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F0DD2C7A1D18386300654345 /* ContactDetailsViewController.xib */; };
F0FE6F7A1D63752A0004E747 /* CallViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F0FE6F781D63752A0004E747 /* CallViewController.m */; };
@ -641,6 +644,12 @@
F03BF6481D8BF5B1002EF6A7 /* voice_call_icon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "voice_call_icon@3x.png"; sourceTree = "<group>"; };
F03DE2A31D0EFA6A00E8B65C /* AttachmentsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AttachmentsViewController.h; sourceTree = "<group>"; };
F03DE2A41D0EFA6A00E8B65C /* AttachmentsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AttachmentsViewController.m; sourceTree = "<group>"; };
F046528B1E250B0A00EA4E77 /* ContactsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactsTableViewController.h; sourceTree = "<group>"; };
F046528C1E250B0A00EA4E77 /* ContactsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContactsTableViewController.m; sourceTree = "<group>"; };
F046528E1E28439E00EA4E77 /* ContactsTableViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContactsTableViewController.xib; sourceTree = "<group>"; };
F04652901E28E0E300EA4E77 /* add_participant.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = add_participant.png; sourceTree = "<group>"; };
F04652911E28E0E300EA4E77 /* add_participant@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "add_participant@2x.png"; sourceTree = "<group>"; };
F04652921E28E0E300EA4E77 /* add_participant@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "add_participant@3x.png"; sourceTree = "<group>"; };
F047DBB41C576F2200952DA2 /* AuthenticationViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AuthenticationViewController.xib; sourceTree = "<group>"; };
F047DBB61C576F6600952DA2 /* AuthInputsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AuthInputsView.h; sourceTree = "<group>"; };
F047DBB71C576F6600952DA2 /* AuthInputsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AuthInputsView.m; sourceTree = "<group>"; };
@ -889,9 +898,6 @@
F0D2D9801C197DCB007B8C96 /* RoomIncomingTextMsgWithoutSenderInfoBubbleCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RoomIncomingTextMsgWithoutSenderInfoBubbleCell.h; sourceTree = "<group>"; };
F0D2D9811C197DCB007B8C96 /* RoomIncomingTextMsgWithoutSenderInfoBubbleCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RoomIncomingTextMsgWithoutSenderInfoBubbleCell.m; sourceTree = "<group>"; };
F0D2D9821C197DCB007B8C96 /* RoomIncomingTextMsgWithoutSenderInfoBubbleCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RoomIncomingTextMsgWithoutSenderInfoBubbleCell.xib; sourceTree = "<group>"; };
F0DD2C6E1D1308E800654345 /* ContactPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactPickerViewController.h; sourceTree = "<group>"; };
F0DD2C6F1D1308E800654345 /* ContactPickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContactPickerViewController.m; sourceTree = "<group>"; };
F0DD2C711D141B0600654345 /* ContactPickerViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContactPickerViewController.xib; sourceTree = "<group>"; };
F0DD2C781D18386300654345 /* ContactDetailsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactDetailsViewController.h; sourceTree = "<group>"; };
F0DD2C791D18386300654345 /* ContactDetailsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContactDetailsViewController.m; sourceTree = "<group>"; };
F0DD2C7A1D18386300654345 /* ContactDetailsViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContactDetailsViewController.xib; sourceTree = "<group>"; };
@ -1144,6 +1150,9 @@
F03BF5B41D8BF5B1002EF6A7 /* Images */ = {
isa = PBXGroup;
children = (
F04652901E28E0E300EA4E77 /* add_participant.png */,
F04652911E28E0E300EA4E77 /* add_participant@2x.png */,
F04652921E28E0E300EA4E77 /* add_participant@3x.png */,
F04ACE001E154C540000B970 /* riot_icon.png */,
F04ACE011E154C540000B970 /* riot_icon@2x.png */,
F04ACE021E154C540000B970 /* riot_icon@3x.png */,
@ -1523,6 +1532,9 @@
F094AA151B78E42600B1FBBF /* ViewController */ = {
isa = PBXGroup;
children = (
F046528B1E250B0A00EA4E77 /* ContactsTableViewController.h */,
F046528C1E250B0A00EA4E77 /* ContactsTableViewController.m */,
F046528E1E28439E00EA4E77 /* ContactsTableViewController.xib */,
F0AC73461DA2A6130011DAEE /* RoomFilesSearchViewController.h */,
F0AC73471DA2A6130011DAEE /* RoomFilesSearchViewController.m */,
F0AC73481DA2A6130011DAEE /* RoomMessagesSearchViewController.h */,
@ -1536,9 +1548,6 @@
F0DD2C781D18386300654345 /* ContactDetailsViewController.h */,
F0DD2C791D18386300654345 /* ContactDetailsViewController.m */,
F0DD2C7A1D18386300654345 /* ContactDetailsViewController.xib */,
F0DD2C6E1D1308E800654345 /* ContactPickerViewController.h */,
F0DD2C6F1D1308E800654345 /* ContactPickerViewController.m */,
F0DD2C711D141B0600654345 /* ContactPickerViewController.xib */,
F03DE2A31D0EFA6A00E8B65C /* AttachmentsViewController.h */,
F03DE2A41D0EFA6A00E8B65C /* AttachmentsViewController.m */,
F0BE3DEE1C6CE17200AC3111 /* RoomMemberDetailsViewController.h */,
@ -1793,6 +1802,7 @@
files = (
F0D2D9881C197DCB007B8C96 /* RoomIncomingTextMsgBubbleCell.xib in Resources */,
F03BF6CB1D8BF5B1002EF6A7 /* settings_icon.png in Resources */,
F04652931E28E0E300EA4E77 /* add_participant.png in Resources */,
F03BF6791D8BF5B1002EF6A7 /* chevron@3x.png in Resources */,
F03BF64B1D8BF5B1002EF6A7 /* admin_icon@3x.png in Resources */,
F09EAFB61DD2109B009C7EFB /* RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib in Resources */,
@ -1828,6 +1838,7 @@
7165A25C1C05CD42003635D7 /* SegmentedViewController.xib in Resources */,
F03BF65F1D8BF5B1002EF6A7 /* call_speaker_off_icon@2x.png in Resources */,
F0A4B2F11E0073A30072D355 /* animatedLogo-1.png in Resources */,
F04652941E28E0E300EA4E77 /* add_participant@2x.png in Resources */,
F03BF6B41D8BF5B1002EF6A7 /* priorityLow@2x.png in Resources */,
F08294691DB503FE00CEAB63 /* direct_icon@2x.png in Resources */,
F04ACE041E154C540000B970 /* riot_icon@2x.png in Resources */,
@ -1848,7 +1859,6 @@
F03BF6AF1D8BF5B1002EF6A7 /* plus_icon@3x.png in Resources */,
F03BF6C91D8BF5B1002EF6A7 /* selection_untick@2x.png in Resources */,
71046D601C0C86C600DCA984 /* RoomTitleView.xib in Resources */,
F0DD2C721D141B0600654345 /* ContactPickerViewController.xib in Resources */,
F083C4681D9E9F8800E5246C /* MessagesSearchResultTextMsgBubbleCell.xib in Resources */,
F03BF6D51D8BF5B1002EF6A7 /* upload_icon@2x.png in Resources */,
F03BF6CE1D8BF5B1002EF6A7 /* shrink_icon.png in Resources */,
@ -1890,12 +1900,14 @@
F0BE3DF21C6CE28300AC3111 /* RoomMemberDetailsViewController.xib in Resources */,
F09EAF9C1DD2109B009C7EFB /* RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.xib in Resources */,
F023A0161D9034FE00C517FB /* call_video_mute_on_icon.png in Resources */,
F04652951E28E0E300EA4E77 /* add_participant@3x.png in Resources */,
F03BF66A1D8BF5B1002EF6A7 /* camera_capture.png in Resources */,
F0AF11F61D1029CF00FEE52F /* RoomIdOrAliasTableViewCell.xib in Resources */,
F08714CC1DB9EFEE0075F633 /* directChatOff@3x.png in Resources */,
F0C34CB21C16269D00C36F09 /* RoomIncomingAttachmentWithPaginationTitleBubbleCell.xib in Resources */,
F083C4661D9E9F8800E5246C /* MessagesSearchResultAttachmentBubbleCell.xib in Resources */,
F03BF6731D8BF5B1002EF6A7 /* camera_switch.png in Resources */,
F046528F1E28439E00EA4E77 /* ContactsTableViewController.xib in Resources */,
F03BF64E1D8BF5B1002EF6A7 /* back_icon@3x.png in Resources */,
F083C4961D9EAFC500E5246C /* file_video_icon.png in Resources */,
F03BF6911D8BF5B1002EF6A7 /* group@3x.png in Resources */,
@ -2140,7 +2152,6 @@
32A887211C89B9580037DC17 /* SimpleRoomTitleView.m in Sources */,
F056417B1C7C9FD7002276ED /* TableViewCellWithButton.m in Sources */,
F0CC4DC01C4E26FA003BBE45 /* MediaAlbumTableCell.m in Sources */,
F0DD2C701D1308E800654345 /* ContactPickerViewController.m in Sources */,
F022285B1C64D529000AF23C /* ExpandedRoomTitleView.m in Sources */,
F01214D11DABD69D00755336 /* RoomFilesViewController.m in Sources */,
F0C34B671C15C28300C36F09 /* RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m in Sources */,
@ -2176,6 +2187,7 @@
F094A9A81B78D8F000B1FBBF /* main.m in Sources */,
F09EAFB51DD2109B009C7EFB /* RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */,
F00C47861BFF77C800DBABC9 /* RecentTableViewCell.m in Sources */,
F046528D1E250B0A00EA4E77 /* ContactsTableViewController.m in Sources */,
F0CC4DCA1C4E594C003BBE45 /* MediaAlbumContentViewController.m in Sources */,
F09EAF9D1DD2109B009C7EFB /* RoomIncomingEncryptedTextMsgBubbleCell.m in Sources */,
F0B7037E1D22D4AD00B63766 /* TableViewCellWithCheckBoxAndLabel.m in Sources */,

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View file

@ -95,6 +95,7 @@
"room_creation_keep_private" = "Keep private";
"room_creation_make_private" = "Make private";
"room_creation_wait_for_creation" = "A room is already being created. Please wait.";
"room_creation_invite_another_user" = "Search / invite by User ID, Name or email";
// Room recents
"room_recents_directory" = "DIRECTORY";
@ -110,6 +111,8 @@
"search_messages" = "Messages";
"search_people" = "People";
"search_files" = "Files";
"search_default_placeholder" = "Search...";
"search_people_placeholder" = "Search by User ID, Name or email";
// Directory
"directory_cell_title" = "Browse directory";
@ -119,6 +122,10 @@
"directory_searching_title" = "Searching directory...";
"directory_search_fail" = "Failed to fetch data";
// Contacts
"contacts_address_book_section" = "LOCAL CONTACTS";
"contacts_matrix_users_section" = "KNOWN CONTACTS";
// Chat participants
"room_participants_title" = "Participants";
"room_participants_add_participant" = "Add participant";
@ -130,12 +137,10 @@
"room_participants_remove_prompt_msg" = "Are you sure you want to remove %@ from this chat?";
"room_participants_invite_prompt_title" = "Invite?";
"room_participants_invite_prompt_msg" = "Are you sure you want to invite %@ to this chat?";
"room_participants_invite_another_user" = "Search / invite by name, email, id";
"room_participants_filter_room_members" = "Filter room members";
"room_participants_invite_another_user" = "Search / invite by User ID, Name or email";
"room_participants_invite_malformed_id_title" = "Invite Error";
"room_participants_invite_malformed_id" = "Malformed ID. Should be an email address or a Matrix ID like '@localpart:domain'";
"room_participants_address_book_section" = "CONTACT BOOK";
"room_participants_matrix_users_section" = "ROOM CONTACTS";
"room_participants_invited_section" = "INVITED";
"room_participants_online" = "Online";

View file

@ -1,69 +0,0 @@
/*
Copyright 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import <MatrixKit/MatrixKit.h>
@class ContactPickerViewController;
/**
`ContactPickerViewController` delegate.
*/
@protocol ContactPickerViewControllerDelegate <NSObject>
/**
Tells the delegate that the user selected a contact.
@param contactPickerViewController the `ContactPickerViewController` instance.
@param contact the selected contact.
*/
- (void)contactPickerViewController:(ContactPickerViewController *)contactPickerViewController didSelectContact:(MXKContact*)contact;
@end
/**
`ContactPickerViewController` displays people search in user's rooms under a `HomeViewController` segment.
*/
@interface ContactPickerViewController : MXKViewController <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic) IBOutlet UITableView *contactsTableView;
@property (weak, nonatomic) IBOutlet UILabel *noResultsLabel;
/**
The delegate for the view controller.
*/
@property (nonatomic) id<ContactPickerViewControllerDelegate> delegate;
/**
Returns the `UINib` object initialized for a `ContactPickerViewController`.
@return The initialized `UINib` object or `nil` if there were errors during initialization
or the nib file could not be located.
*/
+ (UINib *)nib;
/**
Creates and returns a new `ContactPickerViewController` object.
@discussion This is the designated initializer for programmatic instantiation.
@return An initialized `ContactPickerViewController` object if successful, `nil` otherwise.
*/
+ (instancetype)contactPickerViewController;
/**
*/
- (void)searchWithPattern:(NSString *)searchText;
@end

View file

@ -1,373 +0,0 @@
/*
Copyright 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "ContactPickerViewController.h"
#import "AppDelegate.h"
#import "ContactTableViewCell.h"
#import "VectorDesignValues.h"
#import "RageShakeManager.h"
@interface ContactPickerViewController()
{
NSMutableArray *matrixContacts;
NSMutableArray *filteredContacts;
NSString *currentSearchText;
// This dictionary tells for each display name whether it appears several times.
NSMutableDictionary <NSString*,NSNumber*> *isMultiUseNameByDisplayName;
NSMutableDictionary <NSString*,NSNumber*> *backupIsMultiUseNameByDisplayName;
// Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar.
id kAppDelegateDidTapStatusBarNotificationObserver;
}
@end
@implementation ContactPickerViewController
#pragma mark - Class methods
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([ContactPickerViewController class])
bundle:[NSBundle bundleForClass:[ContactPickerViewController class]]];
}
+ (instancetype)contactPickerViewController
{
return [[[self class] alloc] initWithNibName:NSStringFromClass([ContactPickerViewController class])
bundle:[NSBundle bundleForClass:[ContactPickerViewController class]]];
}
#pragma mark -
- (void)finalizeInit
{
[super finalizeInit];
// Setup `MXKViewControllerHandling` properties
self.defaultBarTintColor = kVectorNavBarTintColor;
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Check whether the view controller has been pushed via storyboard
if (!_contactsTableView)
{
// Instantiate view controller objects
[[[self class] nib] instantiateWithOwner:self options:nil];
}
[self.contactsTableView registerNib:ContactTableViewCell.nib forCellReuseIdentifier:ContactTableViewCell.defaultReuseIdentifier];
// Hide line separators of empty cells
self.contactsTableView.tableFooterView = [[UIView alloc] init];
self.noResultsLabel.text = [NSBundle mxk_localizedStringForKey:@"search_no_results"];
self.noResultsLabel.hidden = YES;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking (via Google Analytics)
id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker];
if (tracker)
{
[tracker set:kGAIScreenName value:@"PeopleGlobalSearch"];
[tracker send:[[GAIDictionaryBuilder createScreenView] build]];
}
// Observe kAppDelegateDidTapStatusBarNotificationObserver.
kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
[self.contactsTableView setContentOffset:CGPointMake(-self.contactsTableView.contentInset.left, -self.contactsTableView.contentInset.top) animated:YES];
}];
// Register on contact update
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshContactsList) name:kMXKContactManagerDidUpdateMatrixContactsNotification object:nil];
[self refreshContactsList];
// Check whether the access to the local contacts has not been already asked.
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined)
{
// Allow by default the local contacts sync in order to discover matrix users.
// This setting change will trigger the loading of the local contacts, which will automatically
// ask user permission to access their local contacts.
[MXKAppSettings standardAppSettings].syncLocalContacts = YES;
}
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKContactManagerDidUpdateMatrixContactsNotification object:nil];
if (kAppDelegateDidTapStatusBarNotificationObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:kAppDelegateDidTapStatusBarNotificationObserver];
kAppDelegateDidTapStatusBarNotificationObserver = nil;
}
}
- (void)destroy
{
matrixContacts = nil;
filteredContacts = nil;
isMultiUseNameByDisplayName = nil;
backupIsMultiUseNameByDisplayName = nil;
[super destroy];
}
#pragma mark -
- (void)refreshContactsList
{
// Retrieve all the known matrix users
NSArray *contacts = [NSArray arrayWithArray:[MXKContactManager sharedManager].matrixContacts];
// Retrieve all the local contacts with methods
NSArray *localContactsWithMethods = [MXKContactManager sharedManager].localContactsWithMethods;
matrixContacts = [NSMutableArray arrayWithCapacity:(contacts.count + localContactsWithMethods.count)];
// Add first email contacts
if (localContactsWithMethods.count)
{
[matrixContacts addObjectsFromArray:localContactsWithMethods];
}
if (contacts.count)
{
[matrixContacts addObjectsFromArray:contacts];
}
// Sort invitable contacts by displaying local email first
// ...and then alphabetically.
NSComparator comparator = ^NSComparisonResult(MXKContact *contactA, MXKContact *contactB) {
BOOL isLocalEmailA = !contactA.matrixIdentifiers.count;
BOOL isLocalEmailB = !contactB.matrixIdentifiers.count;
if (!isLocalEmailA && isLocalEmailB)
{
return NSOrderedDescending;
}
if (isLocalEmailA && !isLocalEmailB)
{
return NSOrderedAscending;
}
return [contactA.sortingDisplayName compare:contactB.sortingDisplayName options:NSCaseInsensitiveSearch];
};
[matrixContacts sortUsingComparator:comparator];
// Reset
isMultiUseNameByDisplayName = [NSMutableDictionary dictionary];
backupIsMultiUseNameByDisplayName = nil;
if (currentSearchText.length)
{
filteredContacts = [NSMutableArray array];
for (MXKContact* contact in matrixContacts)
{
if ([contact matchedWithPatterns:@[currentSearchText]])
{
[filteredContacts addObject:contact];
isMultiUseNameByDisplayName[contact.displayName] = (isMultiUseNameByDisplayName[contact.displayName] ? @(YES) : @(NO));
}
}
}
else
{
for (MXKContact* contact in matrixContacts)
{
isMultiUseNameByDisplayName[contact.displayName] = (isMultiUseNameByDisplayName[contact.displayName] ? @(YES) : @(NO));
}
backupIsMultiUseNameByDisplayName = isMultiUseNameByDisplayName;
}
// Refresh display
[self.contactsTableView reloadData];
}
- (void)searchWithPattern:(NSString *)searchText
{
NSArray *contacts;
// Update search results
if (currentSearchText.length && [searchText hasPrefix:currentSearchText])
{
contacts = filteredContacts;
}
else
{
contacts = matrixContacts;
}
currentSearchText = searchText;
if (currentSearchText.length)
{
// Check whether the search input is a valid email or a Matrix user ID
BOOL isValidInput = ([MXTools isEmailAddress:currentSearchText] || [MXTools isMatrixUserIdentifier:currentSearchText]);
filteredContacts = [NSMutableArray array];
isMultiUseNameByDisplayName = [NSMutableDictionary dictionary];
for (MXKContact* contact in contacts)
{
if ([contact matchedWithPatterns:@[currentSearchText]])
{
// Ignore the contact if it corresponds to the search input
if (!isValidInput || [contact.displayName isEqualToString:currentSearchText] == NO)
{
[filteredContacts addObject:contact];
isMultiUseNameByDisplayName[contact.displayName] = (isMultiUseNameByDisplayName[contact.displayName] ? @(YES) : @(NO));
}
}
}
// Show what the user is typing in a cell. So that he can click on it
MXKContact *contact = [[MXKContact alloc] initMatrixContactWithDisplayName:currentSearchText andMatrixID:nil];
[filteredContacts insertObject:contact atIndex:0];
}
else
{
filteredContacts = nil;
if (backupIsMultiUseNameByDisplayName)
{
isMultiUseNameByDisplayName = backupIsMultiUseNameByDisplayName;
}
else
{
for (MXKContact* contact in matrixContacts)
{
isMultiUseNameByDisplayName[contact.displayName] = (isMultiUseNameByDisplayName[contact.displayName] ? @(YES) : @(NO));
}
backupIsMultiUseNameByDisplayName = isMultiUseNameByDisplayName;
}
}
// Refresh display
[self.contactsTableView reloadData];
}
#pragma mark - UITableView data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger count = 0;
// Display something only when search is in progress (Hide the full contacts list by default).
if (filteredContacts)
{
count = filteredContacts.count;
_noResultsLabel.hidden = (count != 0);
}
return count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ContactTableViewCell* participantCell = [tableView dequeueReusableCellWithIdentifier:[ContactTableViewCell defaultReuseIdentifier]];
MXKContact *contact;
if (indexPath.row < filteredContacts.count)
{
contact = filteredContacts[indexPath.row];
}
if (contact)
{
participantCell.contentView.alpha = 1.0;
participantCell.userInteractionEnabled = YES;
if (currentSearchText.length && indexPath.row == 0)
{
// This is the text entered by the user
// Check whether the search input is a valid email or a Matrix user ID before adding the plus icon.
if (![MXTools isEmailAddress:currentSearchText] && ![MXTools isMatrixUserIdentifier:currentSearchText])
{
participantCell.contentView.alpha = 0.5;
participantCell.userInteractionEnabled = NO;
}
}
// Disambiguate the display name when it appears several times.
if (contact.displayName)
{
participantCell.showMatrixIdInDisplayName = [isMultiUseNameByDisplayName[contact.displayName] isEqualToNumber:@(YES)];
}
[participantCell render:contact];
}
return participantCell;
}
#pragma mark - UITableView delegate
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 0;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 74.0;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MXKContact *selectedContact;
if (indexPath.row < filteredContacts.count)
{
selectedContact = filteredContacts[indexPath.row];
}
if (_delegate && selectedContact)
{
[self.delegate contactPickerViewController:self didSelectContact:selectedContact];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
@end

View file

@ -0,0 +1,129 @@
/*
Copyright 2017 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import <MatrixKit/MatrixKit.h>
#import "ContactTableViewCell.h"
#import "VectorDesignValues.h"
@class ContactsTableViewController;
/**
`ContactsTableViewController` delegate.
*/
@protocol ContactsTableViewControllerDelegate <NSObject>
/**
Tells the delegate that the user selected a contact.
@param contactsTableViewController the `ContactsTableViewController` instance.
@param contact the selected contact.
*/
- (void)contactsTableViewController:(ContactsTableViewController *)contactsTableViewController didSelectContact:(MXKContact*)contact;
@end
/**
'ContactsTableViewController' instance is used to display/filter a list of contacts.
See 'ContactsTableViewController-inherited' object for example of use.
*/
@interface ContactsTableViewController : MXKViewController <UITableViewDelegate, UITableViewDataSource>
{
@protected
// Section indexes
NSInteger searchInputSection;
NSInteger filteredLocalContactsSection;
NSInteger filteredMatrixContactsSection;
// The contact used to describe the current user.
MXKContact *userContact;
// Search results
NSString *currentSearchText;
NSMutableArray<MXKContact*> *filteredLocalContacts;
NSMutableArray<MXKContact*> *filteredMatrixContacts;
}
/**
Returns the `UINib` object initialized for a `ContactsTableViewController`.
@return The initialized `UINib` object or `nil` if there were errors during initialization
or the nib file could not be located.
*/
+ (UINib *)nib;
/**
Creates and returns a new `ContactsTableViewController` object.
@discussion This is the designated initializer for programmatic instantiation.
@return An initialized `ContactsTableViewController` object if successful, `nil` otherwise.
*/
+ (instancetype)contactsTableViewController;
/**
The contacts table view.
*/
@property (weak, nonatomic) IBOutlet UITableView *tableView;
/**
Tell whether the matrix id should be added by default in the matrix contact display name (NO by default).
If NO, the matrix id is added only to disambiguate the contact display names which appear several times.
*/
@property (nonatomic) BOOL forceMatrixIdInDisplayName;
/**
The type of standard accessory view the contact cells should use
Default is UITableViewCellAccessoryNone.
*/
@property (nonatomic) UITableViewCellAccessoryType contactCellAccessoryType;
/**
An image used to create a custom accessy view on the right side of the contact cells.
If set, use custom view. ignore accessoryType
*/
@property (nonatomic) UIImage *contactCellAccessoryImage;
/**
The dictionary of the ignored local contacts, the keys are their email. Empty by default.
*/
@property (nonatomic) NSMutableDictionary<NSString*, MXKContact*> *ignoredContactsByEmail;
/**
The dictionary of the ignored matrix contacts, the keys are their matrix identifier. Empty by default.
*/
@property (nonatomic) NSMutableDictionary<NSString*, MXKContact*> *ignoredContactsByMatrixId;
/**
Filter the contacts list, by keeping only the contacts who have the search pattern
as prefix in their display name, their matrix identifiers and/or their contact methods (emails, phones).
@param searchText the search pattern (nil to reset filtering).
@param forceReset tell whether the search request must be applied by ignoring the previous search result if any (use NO by default).
*/
- (void)searchWithPattern:(NSString *)searchText forceReset:(BOOL)forceReset;
/**
Refresh the contacts table display.
*/
- (void)refreshTableView;
/**
The delegate for the view controller.
*/
@property (nonatomic) id<ContactsTableViewControllerDelegate> contactsTableViewControllerDelegate;
@end

View file

@ -0,0 +1,660 @@
/*
Copyright 2017 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "ContactsTableViewController.h"
#import "UIViewController+VectorSearch.h"
#import "RageShakeManager.h"
#import "AppDelegate.h"
@interface ContactsTableViewController ()
{
// Search processing
dispatch_queue_t searchProcessingQueue;
NSUInteger searchProcessingCount;
NSString *searchProcessingText;
NSMutableArray<MXKContact*> *searchProcessingLocalContacts;
NSMutableArray<MXKContact*> *searchProcessingMatrixContacts;
// Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar.
id kAppDelegateDidTapStatusBarNotificationObserver;
BOOL forceSearchResultRefresh;
// This dictionary tells for each display name whether it appears several times.
NSMutableDictionary <NSString*,NSNumber*> *isMultiUseNameByDisplayName;
}
@end
@implementation ContactsTableViewController
#pragma mark - Class methods
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([ContactsTableViewController class])
bundle:[NSBundle bundleForClass:[ContactsTableViewController class]]];
}
+ (instancetype)contactsTableViewController
{
return [[[self class] alloc] initWithNibName:NSStringFromClass([ContactsTableViewController class])
bundle:[NSBundle bundleForClass:[ContactsTableViewController class]]];
}
#pragma mark -
- (void)finalizeInit
{
[super finalizeInit];
// Setup `MXKViewControllerHandling` properties
self.defaultBarTintColor = kVectorNavBarTintColor;
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
// Prepare search session
searchProcessingQueue = dispatch_queue_create("StartChatViewController", DISPATCH_QUEUE_SERIAL);
searchProcessingCount = 0;
searchProcessingText = nil;
searchProcessingLocalContacts = nil;
searchProcessingMatrixContacts = nil;
_ignoredContactsByEmail = [NSMutableDictionary dictionary];
_ignoredContactsByMatrixId = [NSMutableDictionary dictionary];
isMultiUseNameByDisplayName = [NSMutableDictionary dictionary];
_forceMatrixIdInDisplayName = NO;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Check whether the view controller has been pushed via storyboard
if (!self.tableView)
{
// Instantiate view controller objects
[[[self class] nib] instantiateWithOwner:self options:nil];
}
// Hide line separators of empty cells
self.tableView.tableFooterView = [[UIView alloc] init];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)destroy
{
filteredLocalContacts = nil;
filteredMatrixContacts = nil;
_ignoredContactsByEmail = nil;
_ignoredContactsByMatrixId = nil;
userContact = nil;
searchProcessingQueue = nil;
searchProcessingLocalContacts = nil;
searchProcessingMatrixContacts = nil;
isMultiUseNameByDisplayName = nil;
_contactCellAccessoryImage = nil;
[super destroy];
}
- (void)addMatrixSession:(MXSession *)mxSession
{
[super addMatrixSession:mxSession];
// FIXME: Handle multi accounts
NSString *displayName = NSLocalizedStringFromTable(@"you", @"Vector", nil);
userContact = [[MXKContact alloc] initMatrixContactWithDisplayName:displayName andMatrixID:self.mainSession.myUser.userId];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking (via Google Analytics)
id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker];
if (tracker)
{
[tracker set:kGAIScreenName value:@"ContactsTable"];
[tracker send:[[GAIDictionaryBuilder createScreenView] build]];
}
// Check whether the access to the local contacts has not been already asked.
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined)
{
// Allow by default the local contacts sync in order to discover matrix users.
// This setting change will trigger the loading of the local contacts, which will automatically
// ask user permission to access their local contacts.
[MXKAppSettings standardAppSettings].syncLocalContacts = YES;
}
// Observe kAppDelegateDidTapStatusBarNotification.
kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
[self.tableView setContentOffset:CGPointMake(-self.tableView.contentInset.left, -self.tableView.contentInset.top) animated:YES];
}];
// Register on contact update notifications
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onContactManagerDidUpdate:) name:kMXKContactManagerDidUpdateMatrixContactsNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onContactManagerDidUpdate:) name:kMXKContactManagerDidUpdateLocalContactsNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onContactManagerDidUpdate:) name:kMXKContactManagerDidUpdateLocalContactMatrixIDsNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (kAppDelegateDidTapStatusBarNotificationObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:kAppDelegateDidTapStatusBarNotificationObserver];
kAppDelegateDidTapStatusBarNotificationObserver = nil;
}
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKContactManagerDidUpdateMatrixContactsNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKContactManagerDidUpdateLocalContactsNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKContactManagerDidUpdateLocalContactMatrixIDsNotification object:nil];
}
#pragma mark -
- (void)setForceMatrixIdInDisplayName:(BOOL)forceMatrixIdInDisplayName
{
if (_forceMatrixIdInDisplayName != forceMatrixIdInDisplayName)
{
_forceMatrixIdInDisplayName = forceMatrixIdInDisplayName;
if (self.tableView)
{
[self refreshTableView];
}
}
}
- (void)searchWithPattern:(NSString *)searchText forceReset:(BOOL)forceRefresh
{
// Update search results.
searchText = [searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
searchProcessingCount++;
[self startActivityIndicator];
dispatch_async(searchProcessingQueue, ^{
if (!searchText.length)
{
searchProcessingLocalContacts = nil;
searchProcessingMatrixContacts = nil;
}
else if (forceRefresh || !searchProcessingText.length || [searchText hasPrefix:searchProcessingText] == NO)
{
// Retrieve all the local contacts
searchProcessingLocalContacts = [self unfilteredLocalContactsArray];
// Retrieve all known matrix users
searchProcessingMatrixContacts = [self unfilteredMatrixContactsArray];
}
for (NSUInteger index = 0; index < searchProcessingLocalContacts.count;)
{
MXKContact* contact = searchProcessingLocalContacts[index];
if (![contact hasPrefix:searchText])
{
[searchProcessingLocalContacts removeObjectAtIndex:index];
}
else
{
// Next
index++;
}
}
for (NSUInteger index = 0; index < searchProcessingMatrixContacts.count;)
{
MXKContact* contact = searchProcessingMatrixContacts[index];
if (![contact hasPrefix:searchText])
{
[searchProcessingMatrixContacts removeObjectAtIndex:index];
}
else
{
// Next
index++;
}
}
// Sort the refreshed list of the invitable contacts
[[MXKContactManager sharedManager] sortAlphabeticallyContacts:searchProcessingLocalContacts];
[[MXKContactManager sharedManager] sortContactsByLastActiveInformation:searchProcessingMatrixContacts];
searchProcessingText = searchText;
dispatch_sync(dispatch_get_main_queue(), ^{
// Render the search result only if there is no other search in progress.
searchProcessingCount --;
if (!searchProcessingCount)
{
if (!forceSearchResultRefresh)
{
[self stopActivityIndicator];
// Update the filtered contacts.
currentSearchText = searchProcessingText;
filteredLocalContacts = searchProcessingLocalContacts;
filteredMatrixContacts = searchProcessingMatrixContacts;
if (!self.forceMatrixIdInDisplayName)
{
[isMultiUseNameByDisplayName removeAllObjects];
for (MXKContact* contact in filteredMatrixContacts)
{
isMultiUseNameByDisplayName[contact.displayName] = (isMultiUseNameByDisplayName[contact.displayName] ? @(YES) : @(NO));
}
}
// Refresh display
[self refreshTableView];
// Force scroll to top
[self.tableView setContentOffset:CGPointMake(-self.tableView.contentInset.left, -self.tableView.contentInset.top) animated:NO];
}
else
{
// Launch a new search
forceSearchResultRefresh = NO;
[self searchWithPattern:searchProcessingText forceReset:YES];
}
}
});
});
}
- (void)refreshTableView
{
[self.tableView reloadData];
}
#pragma mark - Internals
- (void)onContactManagerDidUpdate:(NSNotification *)notif
{
// Check whether a search is in progress
if (searchProcessingCount)
{
forceSearchResultRefresh = YES;
return;
}
// Refresh the search result
[self searchWithPattern:currentSearchText forceReset:YES];
}
- (NSMutableArray<MXKContact*>*)unfilteredLocalContactsArray
{
// Retrieve all the contacts obtained by splitting each local contact by contact method. This list is ordered alphabetically.
NSMutableArray *unfilteredLocalContacts = [NSMutableArray arrayWithArray:[MXKContactManager sharedManager].localContactsSplitByContactMethod];
// Remove the ignored contacts
for (NSUInteger index = 0; index < unfilteredLocalContacts.count;)
{
MXKContact* contact = unfilteredLocalContacts[index];
NSArray *identifiers = contact.matrixIdentifiers;
if (identifiers.count)
{
if ([_ignoredContactsByMatrixId objectForKey:identifiers.firstObject])
{
[unfilteredLocalContacts removeObjectAtIndex:index];
continue;
}
}
else
{
NSArray *emails = contact.emailAddresses;
if (emails.count)
{
MXKEmail *email = emails.firstObject;
if ([_ignoredContactsByEmail objectForKey:email.emailAddress])
{
[unfilteredLocalContacts removeObjectAtIndex:index];
continue;
}
}
}
index++;
}
return unfilteredLocalContacts;
}
- (NSMutableArray<MXKContact*>*)unfilteredMatrixContactsArray
{
NSArray *matrixContacts = [MXKContactManager sharedManager].matrixContacts;
NSMutableArray *unfilteredMatrixContacts = [NSMutableArray arrayWithCapacity:matrixContacts.count];
// Matrix ids: split contacts with several ids, and remove the current participants.
for (MXKContact* contact in matrixContacts)
{
NSArray *identifiers = contact.matrixIdentifiers;
if (identifiers.count > 1)
{
for (NSString *userId in identifiers)
{
if ([_ignoredContactsByMatrixId objectForKey:userId] == nil)
{
MXKContact *splitContact = [[MXKContact alloc] initMatrixContactWithDisplayName:contact.displayName andMatrixID:userId];
[unfilteredMatrixContacts addObject:splitContact];
}
}
}
else if (identifiers.count)
{
NSString *userId = identifiers.firstObject;
if ([_ignoredContactsByMatrixId objectForKey:userId] == nil)
{
[unfilteredMatrixContacts addObject:contact];
}
}
}
return unfilteredMatrixContacts;
}
#pragma mark - UITableView data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSInteger count = 0;
searchInputSection = filteredLocalContactsSection = filteredMatrixContactsSection = -1;
if (currentSearchText.length)
{
searchInputSection = count++;
if (filteredLocalContacts.count)
{
filteredLocalContactsSection = count++;
}
if (filteredMatrixContacts.count)
{
filteredMatrixContactsSection = count++;
}
}
else
{
// Display by default the full address book ordered alphabetically, mixing Matrix enabled and non-Matrix enabled users.
if (!filteredLocalContacts)
{
filteredLocalContacts = [self unfilteredLocalContactsArray];
}
if (filteredLocalContacts.count)
{
filteredLocalContactsSection = count++;
}
}
return count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger count = 0;
if (section == searchInputSection)
{
count = 1;
}
else if (section == filteredLocalContactsSection)
{
count = filteredLocalContacts.count;
}
else if (section == filteredMatrixContactsSection)
{
count = filteredMatrixContacts.count;
}
return count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ContactTableViewCell* contactCell = [tableView dequeueReusableCellWithIdentifier:[ContactTableViewCell defaultReuseIdentifier]];
if (!contactCell)
{
contactCell = [[ContactTableViewCell alloc] init];
}
else
{
// Restore default values
contactCell.accessoryView = nil;
contactCell.contentView.alpha = 1;
contactCell.userInteractionEnabled = YES;
contactCell.accessoryType = UITableViewCellAccessoryNone;
contactCell.accessoryView = nil;
}
MXKContact *contact;
if (indexPath.section == searchInputSection)
{
// Show what the user is typing in a cell. So that he can click on it
contact = [[MXKContact alloc] initMatrixContactWithDisplayName:currentSearchText andMatrixID:nil];
contactCell.selectionStyle = UITableViewCellSelectionStyleDefault;
}
else if (indexPath.section == filteredLocalContactsSection)
{
if (indexPath.row < filteredLocalContacts.count)
{
contact = filteredLocalContacts[indexPath.row];
contactCell.selectionStyle = UITableViewCellSelectionStyleDefault;
contactCell.showMatrixIdInDisplayName = YES;
}
}
else if (indexPath.section == filteredMatrixContactsSection)
{
if (indexPath.row < filteredMatrixContacts.count)
{
contact = filteredMatrixContacts[indexPath.row];
contactCell.selectionStyle = UITableViewCellSelectionStyleDefault;
contactCell.showMatrixIdInDisplayName = self.forceMatrixIdInDisplayName ? YES : [isMultiUseNameByDisplayName[contact.displayName] isEqualToNumber:@(YES)];
}
}
if (contact)
{
[contactCell render:contact];
// The search displays contacts to invite.
if (indexPath.section == filteredLocalContactsSection || indexPath.section == filteredMatrixContactsSection)
{
// Add the right accessory view if any
contactCell.accessoryType = self.contactCellAccessoryType;
contactCell.accessoryView = [[UIImageView alloc] initWithImage:self.contactCellAccessoryImage];
}
else if (indexPath.section == searchInputSection)
{
// This is the text entered by the user
// Check whether the search input is a valid email or a Matrix user ID before adding the accessory view.
if (![MXTools isEmailAddress:currentSearchText] && ![MXTools isMatrixUserIdentifier:currentSearchText])
{
contactCell.contentView.alpha = 0.5;
contactCell.userInteractionEnabled = NO;
}
else
{
// Add the right accessory view if any
contactCell.accessoryType = self.contactCellAccessoryType;
contactCell.accessoryView = [[UIImageView alloc] initWithImage:self.contactCellAccessoryImage];
}
}
}
return contactCell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
return NO;
}
#pragma mark - UITableView delegate
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
if (section == filteredLocalContactsSection || section == filteredMatrixContactsSection)
{
return 30.0;
}
return 0;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView* sectionHeader;
if (section == filteredLocalContactsSection || section == filteredMatrixContactsSection)
{
sectionHeader = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, 30)];
sectionHeader.backgroundColor = kVectorColorLightGrey;
CGRect frame = sectionHeader.frame;
frame.origin.x = 20;
frame.origin.y = 5;
frame.size.width = sectionHeader.frame.size.width - 10;
frame.size.height -= 10;
UILabel *headerLabel = [[UILabel alloc] initWithFrame:frame];
headerLabel.font = [UIFont boldSystemFontOfSize:15.0];
headerLabel.backgroundColor = [UIColor clearColor];
if (section == filteredLocalContactsSection)
{
headerLabel.text = NSLocalizedStringFromTable(@"contacts_address_book_section", @"Vector", nil);
}
else if (section == filteredMatrixContactsSection)
{
headerLabel.text = NSLocalizedStringFromTable(@"contacts_matrix_users_section", @"Vector", nil);
}
[sectionHeader addSubview:headerLabel];
}
return sectionHeader;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 74.0;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.contactsTableViewControllerDelegate)
{
NSInteger row = indexPath.row;
MXKContact *mxkContact;
if (indexPath.section == searchInputSection)
{
mxkContact = [[MXKContact alloc] initMatrixContactWithDisplayName:currentSearchText andMatrixID:nil];
}
else if (indexPath.section == filteredLocalContactsSection)
{
mxkContact = filteredLocalContacts[row];
}
else if (indexPath.section == filteredMatrixContactsSection)
{
mxkContact = filteredMatrixContacts[row];
}
if (mxkContact)
{
[self.contactsTableViewControllerDelegate contactsTableViewController:self didSelectContact:mxkContact];
}
}
// Else do nothing by default - `ContactsTableViewController-inherited` instance must override this method.
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
#pragma mark - UISearchBar delegate
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
[self searchWithPattern:searchText forceReset:NO];
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
// "Done" key has been pressed.
// Check whether the current search input is a valid email or a Matrix user ID
if (currentSearchText.length && ([MXTools isEmailAddress:currentSearchText] || [MXTools isMatrixUserIdentifier:currentSearchText]))
{
// Select the contact related to the search input, rather than having to hit +
if (searchInputSection != -1)
{
[self tableView:self.tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:searchInputSection]];
return;
}
}
// Dismiss keyboard
[searchBar resignFirstResponder];
}
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{
searchBar.text = nil;
// Reset filtering
[self searchWithPattern:nil forceReset:NO];
// Leave search
[searchBar resignFirstResponder];
[self withdrawViewControllerAnimated:YES completion:nil];
}
@end

View file

@ -9,10 +9,9 @@
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ContactPickerViewController">
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ContactsTableViewController">
<connections>
<outlet property="contactsTableView" destination="orV-HH-88x" id="4n5-5q-UyB"/>
<outlet property="noResultsLabel" destination="R0k-Za-T1q" id="B08-vF-6Fy"/>
<outlet property="tableView" destination="orV-HH-88x" id="wUr-Sm-kc8"/>
<outlet property="view" destination="iN0-l3-epB" id="NUQ-LI-M61"/>
</connections>
</placeholder>
@ -24,28 +23,19 @@
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="onDrag" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="orV-HH-88x">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="accessibilityIdentifier" value="ContactsTableVCTableView"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="dataSource" destination="-1" id="kQG-Hx-LNM"/>
<outlet property="delegate" destination="-1" id="pQo-sI-THQ"/>
</connections>
</tableView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="No Results" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="R0k-Za-T1q">
<rect key="frame" x="15" y="16" width="375" height="21"/>
<constraints>
<constraint firstAttribute="height" constant="21" id="RRM-CN-Pq2"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="R0k-Za-T1q" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="16" id="9YV-rq-xXR"/>
<constraint firstItem="orV-HH-88x" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="NaR-eJ-WMj"/>
<constraint firstItem="orV-HH-88x" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="VIq-4L-6ij"/>
<constraint firstItem="R0k-Za-T1q" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="15" id="aaV-Bo-Vcp"/>
<constraint firstAttribute="trailing" secondItem="R0k-Za-T1q" secondAttribute="trailing" constant="-15" id="gSa-cX-Yah"/>
<constraint firstAttribute="bottom" secondItem="orV-HH-88x" secondAttribute="bottom" id="m4x-32-odR"/>
<constraint firstAttribute="trailing" secondItem="orV-HH-88x" secondAttribute="trailing" id="yBp-63-kZi"/>
</constraints>

View file

@ -18,7 +18,7 @@
#import "SegmentedViewController.h"
#import "ContactPickerViewController.h"
#import "ContactsTableViewController.h"
#import "RoomViewController.h"
#import "AuthenticationViewController.h"
@ -26,7 +26,7 @@
/**
The `HomeViewController` screen is the main app screen.
*/
@interface HomeViewController : SegmentedViewController <MXKRecentListViewControllerDelegate, UIGestureRecognizerDelegate, ContactPickerViewControllerDelegate>
@interface HomeViewController : SegmentedViewController <MXKRecentListViewControllerDelegate, UIGestureRecognizerDelegate, ContactsTableViewControllerDelegate>
@property (weak, nonatomic) IBOutlet UIBarButtonItem *settingsBarButtonItem;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *searchBarButtonIem;

View file

@ -46,7 +46,7 @@
HomeFilesSearchViewController *filesSearchViewController;
MXKSearchDataSource *filesSearchDataSource;
ContactPickerViewController *contactsViewController;
ContactsTableViewController *peopleSearchViewController;
MXKContact *selectedContact;
// Display a gradient view above the screen
@ -104,9 +104,10 @@
// Add search People tab
[titles addObject: NSLocalizedStringFromTable(@"search_people", @"Vector", nil)];
contactsViewController = [ContactPickerViewController contactPickerViewController];
contactsViewController.delegate = self;
[viewControllers addObject:contactsViewController];
peopleSearchViewController = [ContactsTableViewController contactsTableViewController];
peopleSearchViewController.contactsTableViewControllerDelegate = self;
peopleSearchViewController.contactCellAccessoryType = UITableViewCellAccessoryDisclosureIndicator;
[viewControllers addObject:peopleSearchViewController];
// add Files tab
[titles addObject: NSLocalizedStringFromTable(@"search_files", @"Vector", nil)];
@ -129,6 +130,7 @@
[self initializeDataSources];
self.searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
self.searchBar.placeholder = NSLocalizedStringFromTable(@"search_default_placeholder", @"Vector", nil);
}
- (void)dealloc
@ -511,9 +513,9 @@
{
self.backgroundImageView.hidden = ((messagesSearchDataSource.serverCount != 0) || !messagesSearchViewController.noResultsLabel.isHidden || (self.keyboardHeight == 0));
}
else if (self.selectedViewController == contactsViewController)
else if (self.selectedViewController == peopleSearchViewController)
{
self.backgroundImageView.hidden = (([contactsViewController.contactsTableView numberOfRowsInSection:0] != 0) || !contactsViewController.noResultsLabel.isHidden || (self.keyboardHeight == 0));
self.backgroundImageView.hidden = (([peopleSearchViewController.tableView numberOfRowsInSection:0] != 0) || (self.keyboardHeight == 0));
}
else if (self.selectedViewController == filesSearchViewController)
{
@ -546,6 +548,15 @@
if (!self.searchBarHidden)
{
if (self.selectedViewController == peopleSearchViewController)
{
self.searchBar.placeholder = NSLocalizedStringFromTable(@"search_people_placeholder", @"Vector", nil);
}
else
{
self.searchBar.placeholder = NSLocalizedStringFromTable(@"search_default_placeholder", @"Vector", nil);
}
[self updateSearch];
}
}
@ -1108,9 +1119,9 @@
});
}
}
else if (self.selectedViewController == contactsViewController)
else if (self.selectedViewController == peopleSearchViewController)
{
[contactsViewController searchWithPattern:self.searchBar.text];
[peopleSearchViewController searchWithPattern:self.searchBar.text forceReset:NO];
}
else if (self.selectedViewController == filesSearchViewController)
{
@ -1138,7 +1149,7 @@
{
[messagesSearchDataSource searchMessages:nil force:NO];
}
[contactsViewController searchWithPattern:nil];
[peopleSearchViewController searchWithPattern:nil forceReset:NO];
if (filesSearchDataSource.searchText.length)
{
[filesSearchDataSource searchMessages:nil force:NO];
@ -1157,7 +1168,7 @@
// As the public room search is local, it can be updated on each text change
[self updateSearch];
}
else if (self.selectedViewController == contactsViewController)
else if (self.selectedViewController == peopleSearchViewController)
{
// As the contact search is local, it can be updated on each text change
[self updateSearch];
@ -1188,9 +1199,9 @@
[self selectRoomWithId:roomId andEventId:nil inMatrixSession:matrixSession];
}
#pragma mark - ContactPickerViewControllerDelegate
#pragma mark - ContactsTableViewControllerDelegate
- (void)contactPickerViewController:(ContactPickerViewController *)contactPickerViewController didSelectContact:(MXKContact*)contact
- (void)contactsTableViewController:(ContactsTableViewController *)contactsTableViewController didSelectContact:(MXKContact*)contact
{
selectedContact = contact;

View file

@ -14,10 +14,10 @@
limitations under the License.
*/
#import <MatrixKit/MatrixKit.h>
#import "SegmentedViewController.h"
#import "ContactsTableViewController.h"
@class Contact;
@class RoomParticipantsViewController;
@ -42,7 +42,7 @@
'RoomParticipantsViewController' instance is used to edit members of the room defined by the property 'mxRoom'.
When this property is nil, the view controller is empty.
*/
@interface RoomParticipantsViewController : MXKViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, MXKRoomMemberDetailsViewControllerDelegate>
@interface RoomParticipantsViewController : MXKViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UIGestureRecognizerDelegate, MXKRoomMemberDetailsViewControllerDelegate, ContactsTableViewControllerDelegate>
{
@protected
/**
@ -50,9 +50,6 @@
*/
NSInteger participantsSection;
NSInteger invitedSection;
NSInteger invitableSectionSearchInput;
NSInteger invitableSectionAddressBookContacts;
NSInteger invitableSectionMatrixContacts;
/**
The current list of joined members.
@ -67,7 +64,7 @@
/**
The contact used to describe the current user (nil if the user is not a participant of the room).
*/
Contact *userContact;
Contact *userParticipant;
}
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@ -75,7 +72,6 @@
@property (weak, nonatomic) IBOutlet UISearchBar *searchBarView;
@property (weak, nonatomic) IBOutlet UIView *searchBarHeaderBorder;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *searchBarTopConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *tableViewBottomConstraint;
@ -84,11 +80,6 @@
*/
@property (nonatomic) MXRoom *mxRoom;
/**
Tell whether a search session is in progress
*/
@property (nonatomic) BOOL isAddParticipantSearchBarEditing;
/**
Enable mention option in member details view. NO by default
*/

File diff suppressed because it is too large Load diff

View file

@ -14,16 +14,13 @@
limitations under the License.
*/
#import <MatrixKit/MatrixKit.h>
#import "ContactTableViewCell.h"
#import "ContactsTableViewController.h"
/**
'StartChatViewController' instance is used to prepare new room creation.
*/
@interface StartChatViewController : MXKViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate>
@interface StartChatViewController : ContactsTableViewController <UISearchBarDelegate, ContactsTableViewControllerDelegate>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet UIView *searchBarHeader;
@property (weak, nonatomic) IBOutlet UISearchBar *searchBarView;
@property (weak, nonatomic) IBOutlet UIView *searchBarHeaderBorder;
@ -50,7 +47,7 @@
@discussion This is the designated initializer for programmatic instantiation.
@return An initialized `StartChatViewController` object if successful, `nil` otherwise.
*/
+ (instancetype)roomParticipantsViewController;
+ (instancetype)startChatViewController;
@end

View file

@ -16,26 +16,16 @@
#import "StartChatViewController.h"
#import "VectorDesignValues.h"
#import "RageShakeManager.h"
#import "AppDelegate.h"
#import "AvatarGenerator.h"
@interface StartChatViewController ()
{
// Section indexes
NSInteger participantsSection;
NSInteger invitableSection;
// The current list of participants.
NSMutableArray<MXKContact*> *participants;
// The contact used to describe the current user.
MXKContact *userContact;
// Navigation bar items
UIBarButtonItem *cancelBarButtonItem;
UIBarButtonItem *createBarButtonItem;
@ -43,26 +33,8 @@
// HTTP Request
MXHTTPOperation *roomCreationRequest;
// Search processing
dispatch_queue_t searchProcessingQueue;
NSUInteger searchProcessingCount;
NSString *searchProcessingText;
NSMutableArray<MXKContact*> *searchProcessingContacts;
// Search results
NSString *currentSearchText;
NSMutableArray<MXKContact*> *invitableContacts;
// Contact instances by matrix user id, or email address.
NSMutableDictionary<NSString*, MXKContact*> *participantsById;
// This dictionary tells for each display name whether it appears several times in participants list
NSMutableDictionary <NSString*, NSNumber*> *isMultiUseNameByDisplayName;
MXKAlert *currentAlert;
// Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar.
id kAppDelegateDidTapStatusBarNotificationObserver;
}
@end
@ -77,7 +49,7 @@
bundle:[NSBundle bundleForClass:[StartChatViewController class]]];
}
+ (instancetype)roomParticipantsViewController
+ (instancetype)startChatViewController
{
return [[[self class] alloc] initWithNibName:NSStringFromClass([StartChatViewController class])
bundle:[NSBundle bundleForClass:[StartChatViewController class]]];
@ -89,21 +61,19 @@
{
[super finalizeInit];
// Setup `MXKViewControllerHandling` properties
self.defaultBarTintColor = kVectorNavBarTintColor;
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
self.forceMatrixIdInDisplayName = YES;
_isAddParticipantSearchBarEditing = NO;
// Prepare room participants
participants = [NSMutableArray array];
// Prepare search session
searchProcessingQueue = dispatch_queue_create("StartChatViewController", DISPATCH_QUEUE_SERIAL);
searchProcessingCount = 0;
searchProcessingText = nil;
searchProcessingContacts = nil;
// Assign itself as delegate
self.contactsTableViewControllerDelegate = self;
// 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.
self.contactCellAccessoryImage = [UIImage imageNamed:@"plus_icon"];;
}
- (void)viewDidLoad
@ -112,7 +82,7 @@
// Do any additional setup after loading the view, typically from a nib.
// Check whether the view controller has been pushed via storyboard
if (!_tableView)
if (!self.tableView)
{
// Instantiate view controller objects
[[[self class] nib] instantiateWithOwner:self options:nil];
@ -141,20 +111,20 @@
self.navigationItem.title = NSLocalizedStringFromTable(@"room_creation_title", @"Vector", nil);
cancelBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(onButtonPressed:)];
self.navigationItem.leftBarButtonItem = cancelBarButtonItem;
createBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedStringFromTable(@"start", @"Vector", nil) style:UIBarButtonItemStylePlain target:self action:@selector(onButtonPressed:)];
self.navigationItem.rightBarButtonItem = createBarButtonItem;
// Add each matrix session, to update the view controller appearance according to mx sessions state
// Add each matrix session by default.
NSArray *sessions = [AppDelegate theDelegate].mxSessions;
for (MXSession *mxSession in sessions)
{
[self addMatrixSession:mxSession];
}
_searchBarView.placeholder = NSLocalizedStringFromTable(@"room_participants_invite_another_user", @"Vector", nil);
cancelBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(onButtonPressed:)];
self.navigationItem.leftBarButtonItem = cancelBarButtonItem;
createBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedStringFromTable(@"start", @"Vector", nil) style:UIBarButtonItemStylePlain target:self action:@selector(onButtonPressed:)];
self.navigationItem.rightBarButtonItem = createBarButtonItem;
_searchBarView.placeholder = NSLocalizedStringFromTable(@"room_creation_invite_another_user", @"Vector", nil);
_searchBarView.returnKeyType = UIReturnKeyDone;
_searchBarView.autocapitalizationType = UITextAutocapitalizationTypeNone;
[self refreshSearchBarItemsColor:_searchBarView];
@ -164,15 +134,7 @@
// Hide line separators of empty cells
self.tableView.tableFooterView = [[UIView alloc] init];
// FIXME: Handle multi accounts
NSString *displayName = NSLocalizedStringFromTable(@"you", @"Vector", nil);
userContact = [[MXKContact alloc] initMatrixContactWithDisplayName:displayName andMatrixID:self.mainSession.myUser.userId];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
[self.tableView registerClass:ContactTableViewCell.class forCellReuseIdentifier:@"ParticipantTableViewCellId"];
}
- (void)destroy
@ -186,27 +148,20 @@
cancelBarButtonItem = nil;
createBarButtonItem = nil;
invitableContacts = nil;
participantsById = nil;
isMultiUseNameByDisplayName = nil;
participants = nil;
userContact = nil;
if (currentAlert)
{
[currentAlert dismiss:NO];
currentAlert = nil;
}
searchProcessingQueue = nil;
searchProcessingContacts = nil;
[super destroy];
}
- (void)addMatrixSession:(MXSession *)mxSession
{
[super addMatrixSession:mxSession];
[self refreshParticipants];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
@ -219,16 +174,6 @@
[tracker send:[[GAIDictionaryBuilder createScreenView] build]];
}
// Observe kAppDelegateDidTapStatusBarNotificationObserver.
kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
[self.tableView setContentOffset:CGPointMake(-self.tableView.contentInset.left, -self.tableView.contentInset.top) animated:YES];
}];
// Register on contact update
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshTableView) name:kMXKContactManagerDidUpdateMatrixContactsNotification object:nil];
// Active the search session if the current participant list is empty
if (!participants.count)
{
@ -244,32 +189,18 @@
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (currentAlert)
{
[currentAlert dismiss:NO];
currentAlert = nil;
}
// cancel any pending search
[self searchBarCancelButtonClicked:_searchBarView];
if (kAppDelegateDidTapStatusBarNotificationObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:kAppDelegateDidTapStatusBarNotificationObserver];
kAppDelegateDidTapStatusBarNotificationObserver = nil;
}
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKContactManagerDidUpdateMatrixContactsNotification object:nil];
}
#pragma mark -
- (void)setIsAddParticipantSearchBarEditing:(BOOL)isAddParticipantsSearchBarEditing
- (void)setIsAddParticipantSearchBarEditing:(BOOL)isAddParticipantSearchBarEditing
{
if (_isAddParticipantSearchBarEditing != isAddParticipantsSearchBarEditing)
if (_isAddParticipantSearchBarEditing != isAddParticipantSearchBarEditing)
{
if (isAddParticipantsSearchBarEditing)
if (isAddParticipantSearchBarEditing)
{
self.navigationItem.rightBarButtonItem = nil;
}
@ -280,7 +211,7 @@
[self refreshParticipants];
}
_isAddParticipantSearchBarEditing = isAddParticipantsSearchBarEditing;
_isAddParticipantSearchBarEditing = isAddParticipantSearchBarEditing;
// Switch the display between search result and participants list
[self refreshTableView];
@ -289,58 +220,36 @@
#pragma mark - Internals
- (void)refreshTableView
{
[self.tableView reloadData];
}
- (void)refreshParticipants
{
// Refer all participants in one dictionary.
participantsById = [NSMutableDictionary dictionary];
// Refer all participants in ignored contacts dictionary.
isMultiUseNameByDisplayName = [NSMutableDictionary dictionary];
for (MXKContact* contact in participants)
{
if (contact.matrixIdentifiers.count)
NSArray *identifiers = contact.matrixIdentifiers;
if (identifiers.count)
{
// Here the contact can only have one identifer
[participantsById setObject:contact forKey:contact.matrixIdentifiers.firstObject];
// Here the contact can only have one identifier
[self.ignoredContactsByMatrixId setObject:contact forKey:identifiers.firstObject];
}
else
{
[participantsById setObject:contact forKey:contact.displayName];
NSArray *emails = contact.emailAddresses;
if (emails.count)
{
// Here the contact can only have one email
MXKEmail *email = emails.firstObject;
[self.ignoredContactsByEmail setObject:contact forKey:email.emailAddress];
}
}
isMultiUseNameByDisplayName[contact.displayName] = (isMultiUseNameByDisplayName[contact.displayName] ? @(YES) : @(NO));
}
[participantsById setObject:userContact forKey:self.mainSession.myUser.userId];
}
- (void)sortContacts:(NSMutableArray*)contacts
{
// Sort invitable contacts by displaying local email first
// ...and then alphabetically.
NSComparator comparator = ^NSComparisonResult(MXKContact *contactA, MXKContact *contactB) {
BOOL isLocalEmailA = !contactA.matrixIdentifiers.count;
BOOL isLocalEmailB = !contactB.matrixIdentifiers.count;
if (!isLocalEmailA && isLocalEmailB)
{
return NSOrderedDescending;
}
if (isLocalEmailA && !isLocalEmailB)
{
return NSOrderedAscending;
}
return [contactA.sortingDisplayName compare:contactB.sortingDisplayName options:NSCaseInsensitiveSearch];
};
// Sort invitable contacts list
[contacts sortUsingComparator:comparator];
if (userContact)
{
[self.ignoredContactsByMatrixId setObject:userContact forKey:self.mainSession.myUser.userId];
}
}
#pragma mark - UITableView data source
@ -349,14 +258,14 @@
{
NSInteger count = 0;
invitableSection = participantsSection = -1;
if (_isAddParticipantSearchBarEditing)
{
invitableSection = count++;
participantsSection = -1;
count = [super numberOfSectionsInTableView:self.tableView];
}
else
{
searchInputSection = filteredLocalContactsSection = filteredMatrixContactsSection = -1;
participantsSection = count++;
}
@ -367,46 +276,32 @@
{
NSInteger count = 0;
if (section == invitableSection)
if (_isAddParticipantSearchBarEditing)
{
if (!currentSearchText.length)
{
// Display by default all the contacts who share a private room with the current user
invitableContacts = [NSMutableArray arrayWithArray:[[MXKContactManager sharedManager] privateMatrixContacts:self.mainSession]];
// Sort the refreshed list of the invitable contacts
[self sortContacts:invitableContacts];
}
count = invitableContacts.count;
count = [super tableView:self.tableView numberOfRowsInSection:section];
}
else if (section == participantsSection)
{
count = participants.count + 1;
}
return count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ContactTableViewCell* participantCell = [tableView dequeueReusableCellWithIdentifier:[ContactTableViewCell defaultReuseIdentifier]];
UITableViewCell *cell;
if (!participantCell)
if (_isAddParticipantSearchBarEditing)
{
participantCell = [[ContactTableViewCell alloc] init];
cell = [super tableView:self.tableView cellForRowAtIndexPath:indexPath];
}
else
{
// Restore default values
participantCell.accessoryView = nil;
participantCell.contentView.alpha = 1;
participantCell.userInteractionEnabled = YES;
}
MXKContact *contact;
if (indexPath.section == participantsSection)
else if (indexPath.section == participantsSection)
{
ContactTableViewCell* participantCell = [tableView dequeueReusableCellWithIdentifier:@"ParticipantTableViewCellId" forIndexPath:indexPath];
MXKContact *contact;
if (indexPath.row == 0)
{
// oneself dedicated cell
@ -429,61 +324,12 @@
}
participantCell.selectionStyle = UITableViewCellSelectionStyleNone;
}
else if (indexPath.section == invitableSection)
{
if (indexPath.row < invitableContacts.count)
{
contact = invitableContacts[indexPath.row];
participantCell.selectionStyle = UITableViewCellSelectionStyleDefault;
// Append the matrix identifier (if any) to the display name.
NSArray *identifiers = contact.matrixIdentifiers;
if (identifiers.count)
{
NSString *participantId = identifiers.firstObject;
// Check whether the display name is not already the matrix id
if (![contact.displayName isEqualToString:participantId])
{
participantCell.showMatrixIdInDisplayName = YES;
}
}
}
}
if (contact)
{
[participantCell render:contact];
// The search displays contacts to invite. Add a plus icon to the cell
// in order to make it more understandable for the end user
if (indexPath.section == invitableSection)
{
if (currentSearchText.length && indexPath.row == 0)
{
// This contact corresponds to the text entered by the user
// Check whether this input is a valid email or a Matrix user ID before adding the plus icon.
if (![MXTools isEmailAddress:currentSearchText] && ![MXTools isMatrixUserIdentifier:currentSearchText])
{
participantCell.contentView.alpha = 0.5;
participantCell.userInteractionEnabled = NO;
}
else
{
participantCell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"plus_icon"]];
}
}
else
{
participantCell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"plus_icon"]];
}
}
cell = participantCell;
}
return participantCell;
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
@ -502,31 +348,29 @@
#pragma mark - UITableView delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 74.0;
CGFloat height = 0.0;
if (_isAddParticipantSearchBarEditing)
{
height = [super tableView:self.tableView heightForHeaderInSection:section];
}
return height;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger row = indexPath.row;
if (indexPath.section == invitableSection)
if (_isAddParticipantSearchBarEditing)
{
if (row < invitableContacts.count)
{
MXKContact *mxkContact = invitableContacts[row];
// Update here the mutable list of participants
[participants addObject:mxkContact];
// Refresh display by leaving search session
[self searchBarCancelButtonClicked:_searchBarView];
}
[super tableView:tableView didSelectRowAtIndexPath:indexPath];
}
else
{
// Do nothing
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
@ -742,124 +586,7 @@
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
// Update search results.
searchText = [searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
searchProcessingCount++;
[self startActivityIndicator];
dispatch_async(searchProcessingQueue, ^{
if (!searchText.length)
{
searchProcessingContacts = nil;
}
else if (!searchProcessingText.length || [searchText hasPrefix:searchProcessingText] == NO)
{
// Retrieve all known matrix users
NSArray *matrixContacts = [NSMutableArray arrayWithArray:[MXKContactManager sharedManager].matrixContacts];
// Retrieve all known email addresses from local contacts
NSArray *localContactsWithMethods = [MXKContactManager sharedManager].localContactsWithMethods;
searchProcessingContacts = [NSMutableArray arrayWithCapacity:(matrixContacts.count + localContactsWithMethods.count)];
// Add first email contacts
for (MXKContact* contact in localContactsWithMethods)
{
// Remove the current emails listed in participants.
if ([participantsById objectForKey:contact.displayName] == nil)
{
[searchProcessingContacts addObject:contact];
}
}
// Matrix ids: split contacts with several ids, and remove the current participants.
for (MXKContact* contact in matrixContacts)
{
NSArray *identifiers = contact.matrixIdentifiers;
if (identifiers.count > 1)
{
for (NSString *userId in identifiers)
{
if ([participantsById objectForKey:userId] == nil)
{
MXKContact *splitContact = [[MXKContact alloc] initMatrixContactWithDisplayName:contact.displayName andMatrixID:userId];
[searchProcessingContacts addObject:splitContact];
}
}
}
else if (identifiers.count)
{
NSString *userId = identifiers.firstObject;
if ([participantsById objectForKey:userId] == nil)
{
[searchProcessingContacts addObject:contact];
}
}
}
}
// Check whether the search input is a valid email or a Matrix user ID
BOOL isValidInput = ([MXTools isEmailAddress:searchText] || [MXTools isMatrixUserIdentifier:searchText]);
for (NSUInteger index = 0; index < searchProcessingContacts.count;)
{
MXKContact* contact = searchProcessingContacts[index];
if (![contact hasPrefix:searchText])
{
[searchProcessingContacts removeObjectAtIndex:index];
}
else
{
// Ignore the contact if it corresponds to the search input
if (isValidInput && [contact.displayName isEqualToString:searchText])
{
[searchProcessingContacts removeObjectAtIndex:index];
}
else
{
// Next
index++;
}
}
}
// Sort the refreshed list of the invitable contacts
[self sortContacts:searchProcessingContacts];
if (searchText.length)
{
// Show what the user is typing in a cell. So that he can click on it
MXKContact *contact = [[MXKContact alloc] initMatrixContactWithDisplayName:searchText andMatrixID:nil];
[searchProcessingContacts insertObject:contact atIndex:0];
}
searchProcessingText = searchText;
dispatch_sync(dispatch_get_main_queue(), ^{
// Render the search result only if there is no other search in progress.
searchProcessingCount --;
if (!searchProcessingCount)
{
[self stopActivityIndicator];
// Update the invitable contacts.
currentSearchText = searchProcessingText;
invitableContacts = searchProcessingContacts;
// Refresh display
[self refreshTableView];
// Force scroll to top
[self.tableView setContentOffset:CGPointMake(-self.tableView.contentInset.left, -self.tableView.contentInset.top) animated:YES];
}
});
});
[self searchWithPattern:searchText forceReset:NO];
}
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
@ -887,15 +614,9 @@
if (currentSearchText.length && ([MXTools isEmailAddress:currentSearchText] || [MXTools isMatrixUserIdentifier:currentSearchText]))
{
// Select the contact related to the search input, rather than having to hit +
MXKContact *contact = invitableContacts[0];
// Sanity check
if (contact)
if (searchInputSection != -1)
{
[participants addObject:contact];
// Cancel the search process
[self searchBarCancelButtonClicked:searchBar];
[self tableView:self.tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:searchInputSection]];
return;
}
@ -907,12 +628,28 @@
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{
searchBar.text = currentSearchText = nil;
invitableContacts = nil;
searchBar.text = nil;
self.isAddParticipantSearchBarEditing = NO;
// Reset filtering
[self searchWithPattern:nil forceReset:NO];
// Leave search
[searchBar resignFirstResponder];
}
#pragma mark - ContactsTableViewControllerDelegate
- (void)contactsTableViewController:(ContactsTableViewController *)contactsTableViewController didSelectContact:(MXKContact*)contact
{
if (contact)
{
// Update here the mutable list of participants
[participants addObject:contact];
}
// Refresh display by leaving search session
[self searchBarCancelButtonClicked:_searchBarView];
}
@end

View file

@ -44,6 +44,10 @@
// Clear the default background color of a MXKImageView instance
self.thumbnailView.backgroundColor = [UIColor clearColor];
// Disable by default interactions defined in the cell
// because we want [tableView didSelectRowAtIndexPath:] to be called
self.thumbnailView.userInteractionEnabled = NO;
}
- (void)layoutSubviews