/* Copyright 2024 New Vector Ltd. Copyright 2017 Vector Creations Ltd Copyright 2016 OpenMarket Ltd Copyright (c) 2021 BWI GmbH SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ #import "StartChatViewController.h" #import "GeneratedInterface-Swift.h" #import "MXSession+Riot.h" @interface StartChatViewController () { // The contact used to describe the current user. MXKContact *userContact; // Section indexes NSInteger participantsSection; // The current list of participants. NSMutableArray *participants; // Navigation bar items UIBarButtonItem *cancelBarButtonItem; UIBarButtonItem *createBarButtonItem; // SearchBar text NSString *currentSearch; // HTTP Request MXHTTPOperation *roomCreationRequest; // This dictionary tells for each display name whether it appears several times in participants list NSMutableDictionary *isMultiUseNameByDisplayName; } @property (weak, nonatomic) IBOutlet UIView *searchBarHeader; @property (weak, nonatomic) IBOutlet UISearchBar *searchBarView; @property (weak, nonatomic) IBOutlet UIView *searchBarHeaderBorder; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *searchBarHeaderHeightConstraint; @property (nonatomic, strong) InviteFriendsPresenter *inviteFriendsPresenter; @property (nonatomic, weak) InviteFriendsHeaderView *inviteFriendsHeaderView; @property (nonatomic, weak) UIView *onlyOneEmailInvitationView; @end @implementation StartChatViewController #pragma mark - Class methods + (UINib *)nib { return [UINib nibWithNibName:NSStringFromClass([StartChatViewController class]) bundle:[NSBundle bundleForClass:[StartChatViewController class]]]; } + (instancetype)startChatViewController { return [[[self class] alloc] initWithNibName:NSStringFromClass([StartChatViewController class]) bundle:[NSBundle bundleForClass:[StartChatViewController class]]]; } #pragma mark - - (void)finalizeInit { [super finalizeInit]; _isAddParticipantSearchBarEditing = NO; // Prepare room participants participants = [NSMutableArray array]; // Assign itself as delegate self.contactsTableViewControllerDelegate = self; self.screenTracker = [[AnalyticsScreenTracker alloc] initWithScreen:AnalyticsScreenStartChat]; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. self.navigationItem.title = [BWIL10n roomCreationTitle]; // Add each matrix session by default. NSArray *sessions = [AppDelegate theDelegate].mxSessions; for (MXSession *mxSession in sessions) { [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 = [AssetImages.plusIcon.image vc_tintedImageUsingColor:ThemeService.shared.theme.textPrimaryColor]; [self displayList:dataSource]; cancelBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(onButtonPressed:)]; self.navigationItem.leftBarButtonItem = cancelBarButtonItem; createBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:[VectorL10n create] style:UIBarButtonItemStylePlain target:self action:@selector(onButtonPressed:)]; self.navigationItem.rightBarButtonItem = createBarButtonItem; _searchBarView.placeholder = [BWIL10n roomCreationInviteAnotherUser]; _searchBarView.returnKeyType = UIReturnKeyDone; _searchBarView.autocapitalizationType = UITextAutocapitalizationTypeNone; [self refreshSearchBarItemsColor:_searchBarView]; [self.contactsTableView registerClass:ContactTableViewCell.class forCellReuseIdentifier:@"ParticipantTableViewCellId"]; // Redirect table data source self.contactsTableView.dataSource = self; [self setupInviteFriendsHeaderView]; } - (void)setupInviteFriendsHeaderView { if (self.inviteFriendsHeaderView) { return; } if (!BWIBuildSettings.shared.allowInviteExernalUsers) { self.contactsTableView.tableHeaderView = nil; return; } InviteFriendsHeaderView *inviteFriendsHeaderView = [InviteFriendsHeaderView instantiate]; inviteFriendsHeaderView.delegate = self; self.contactsTableView.tableHeaderView = inviteFriendsHeaderView; self.inviteFriendsHeaderView = inviteFriendsHeaderView; } - (void)showInviteFriendsHeaderView:(BOOL)show { if (!BWIBuildSettings.shared.directChatShowInviteFriends) { show = NO; } if (show) { if (!self.inviteFriendsHeaderView) { [self setupInviteFriendsHeaderView]; } } else if (self.inviteFriendsHeaderView != nil) { self.contactsTableView.tableHeaderView = nil; } } - (void)userInterfaceThemeDidChange { [super userInterfaceThemeDidChange]; [self refreshSearchBarItemsColor:_searchBarView]; // Check the table view style to select its bg color. self.contactsTableView.backgroundColor = ((self.contactsTableView.style == UITableViewStylePlain) ? ThemeService.shared.theme.baseColor : ThemeService.shared.theme.headerBackgroundColor); self.view.backgroundColor = self.contactsTableView.backgroundColor; self.contactsTableView.separatorColor = ThemeService.shared.theme.lineBreakColor; _searchBarHeaderBorder.backgroundColor = self.contactsTableView.backgroundColor; if (self.contactsTableView.dataSource) { [self.contactsTableView reloadData]; } [self.inviteFriendsHeaderView updateWithTheme:ThemeService.shared.theme]; } - (UIStatusBarStyle)preferredStatusBarStyle { return ThemeService.shared.theme.statusBarStyle; } - (void)destroy { if (roomCreationRequest) { [roomCreationRequest cancel]; roomCreationRequest = nil; } cancelBarButtonItem = nil; createBarButtonItem = nil; isMultiUseNameByDisplayName = nil; participants = nil; [super destroy]; } - (void)addMatrixSession:(MXSession *)mxSession { [super addMatrixSession:mxSession]; // FIXME: Handle multi accounts NSString *displayName = [VectorL10n you]; userContact = [[MXKContact alloc] initMatrixContactWithDisplayName:displayName andMatrixID:self.mainSession.myUser.userId]; [self refreshParticipants]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // Active the search session if the current participant list is empty if (!participants.count) { self.isAddParticipantSearchBarEditing = YES; } else { // Refresh display [self refreshContactsTable]; } } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; // cancel any pending search [self searchBarCancelButtonClicked:_searchBarView]; } - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; [self.contactsTableView vc_relayoutHeaderView]; } #pragma mark - - (void)setIsAddParticipantSearchBarEditing:(BOOL)isAddParticipantSearchBarEditing { if (_isAddParticipantSearchBarEditing != isAddParticipantSearchBarEditing) { if (isAddParticipantSearchBarEditing) { self.navigationItem.rightBarButtonItem = nil; } else { self.navigationItem.rightBarButtonItem = createBarButtonItem; [self refreshParticipants]; } _isAddParticipantSearchBarEditing = isAddParticipantSearchBarEditing; // Switch the display between search result and participants list [self refreshContactsTable]; } } #pragma mark - Internals - (void)refreshParticipants { // Refer all participants in ignored contacts dictionary. isMultiUseNameByDisplayName = [NSMutableDictionary dictionary]; [contactsDataSource.ignoredContactsByMatrixId removeAllObjects]; [contactsDataSource.ignoredContactsByEmail removeAllObjects]; for (MXKContact* contact in participants) { NSArray *identifiers = contact.matrixIdentifiers; if (identifiers.count) { // Here the contact can only have one identifier contactsDataSource.ignoredContactsByMatrixId[identifiers.firstObject] = contact; } else { NSArray *emails = contact.emailAddresses; if (emails.count) { // Here the contact can only have one email MXKEmail *email = emails.firstObject; contactsDataSource.ignoredContactsByEmail[email.emailAddress] = contact; } } isMultiUseNameByDisplayName[contact.displayName] = (isMultiUseNameByDisplayName[contact.displayName] ? @(YES) : @(NO)); } if (userContact) { if (self.mainSession.myUser.userId) { contactsDataSource.ignoredContactsByMatrixId[self.mainSession.myUser.userId] = userContact; } } // hide the search bar if a participant is already invited by email [self hideSearchBar: [self participantsAlreadyContainAnEmail]]; } - (BOOL)participantsAlreadyContainAnEmail { for (MXKContact* participant in participants) { // if it is not a matrix contact or a local contact with a MatrixID if (participant.matrixIdentifiers.count == 0 && ![MXTools isMatrixUserIdentifier:participant.displayName]) { return YES; } } return NO; } - (BOOL)canAddParticipant: (MXKContact*) contact { if (!contact) { return YES; } // The following rules will be applied only if the resulting room is going to be encrypted if (![self.mainSession vc_homeserverConfiguration].encryption.isE2EEByDefaultEnabled) { return YES; } // If we have already invited an email, we cannot add another participant if ([self participantsAlreadyContainAnEmail]) { return NO; } // if it is not a matrix contact, nor a local contact with a MatrixID, and if there is already at least one participant, another participant cannot be added. // bwi: dms can only be created with one participant if ((contact.matrixIdentifiers.count == 0 && ![MXTools isMatrixUserIdentifier:contact.displayName]) || participants.count > 0) { return NO; } // Otherwise, we should be able to add this participant return YES; } - (void)showAllowOnlyOneInvitByEmailAllowedHeaderView:(BOOL)visible { if (visible) { if (!self.onlyOneEmailInvitationView) { UIView *headerView = [[UIView alloc] initWithFrame: CGRectZero]; headerView.translatesAutoresizingMaskIntoConstraints = NO; UILabel *label = [[UILabel alloc] initWithFrame: CGRectZero]; label.numberOfLines = 0; label.textColor = ThemeService.shared.theme.textSecondaryColor; label.font = [UIFont systemFontOfSize:14 weight:UIFontWeightLight]; label.adjustsFontSizeToFitWidth = YES; label.text = VectorL10n.roomCreationOnlyOneEmailInvite; label.translatesAutoresizingMaskIntoConstraints = NO; [headerView addSubview:label]; [NSLayoutConstraint activateConstraints:@[ [label.leadingAnchor constraintEqualToAnchor:headerView.leadingAnchor constant:16], [label.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor constant:-16], [label.topAnchor constraintEqualToAnchor:headerView.topAnchor constant:8], [label.bottomAnchor constraintEqualToAnchor:headerView.bottomAnchor constant:-8], ]]; [label setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; [headerView setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; self.onlyOneEmailInvitationView = headerView; self.contactsTableView.tableHeaderView = self.onlyOneEmailInvitationView; [NSLayoutConstraint activateConstraints:@[ [headerView.leadingAnchor constraintEqualToAnchor:self.contactsTableView.safeAreaLayoutGuide.leadingAnchor], [headerView.trailingAnchor constraintEqualToAnchor:self.contactsTableView.safeAreaLayoutGuide.trailingAnchor] ]]; [self.contactsTableView.tableHeaderView layoutIfNeeded]; } } else if (self.onlyOneEmailInvitationView != nil) { if (self.contactsTableView.tableHeaderView == self.onlyOneEmailInvitationView) { self.contactsTableView.tableHeaderView = nil; } self.onlyOneEmailInvitationView = nil; } } - (void)showInviteFriendsFromSourceView:(UIView*)sourceView { if (!self.inviteFriendsPresenter) { self.inviteFriendsPresenter = [InviteFriendsPresenter new]; } NSString *userId = self.mainSession.myUser.userId; [self.inviteFriendsPresenter presentFor:userId from:self sourceView:sourceView animated:YES]; } #pragma mark - UITableView data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { NSInteger count = 0; // Do not show invite friends action when a participant is selected [self showInviteFriendsHeaderView:!participants.count]; if (_isAddParticipantSearchBarEditing) { participantsSection = -1; count = [contactsDataSource numberOfSectionsInTableView:tableView]; } else { participantsSection = count++; } return count; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { NSInteger count = 0; if (_isAddParticipantSearchBarEditing) { count = [contactsDataSource tableView:tableView numberOfRowsInSection:section]; } else if (section == participantsSection) { count = participants.count + 1; } return count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell; if (_isAddParticipantSearchBarEditing) { cell = [contactsDataSource tableView:tableView cellForRowAtIndexPath:indexPath]; MXKContact* contact = [contactsDataSource contactAtIndexPath:indexPath]; if (![self canAddParticipant:contact]) { // Prevent to add it cell.contentView.alpha = 0.5; cell.userInteractionEnabled = NO; cell.accessoryView = nil; } } else if (indexPath.section == participantsSection) { ContactTableViewCell* participantCell = [tableView dequeueReusableCellWithIdentifier:@"ParticipantTableViewCellId" forIndexPath:indexPath]; MXKContact *contact; if (indexPath.row == 0) { // oneself dedicated cell contact = userContact; } else { NSInteger index = indexPath.row - 1; if (index < participants.count) { contact = participants[index]; // Disambiguate the display name when it appears several times. if (contact.displayName) { participantCell.showMatrixIdInDisplayName = [isMultiUseNameByDisplayName[contact.displayName] isEqualToNumber:@(YES)]; } } } participantCell.selectionStyle = UITableViewCellSelectionStyleNone; [participantCell render:contact]; cell = participantCell; } else { // Return a fake cell to prevent app from crashing. cell = [[UITableViewCell alloc] init]; } return cell; } - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == participantsSection && indexPath.row != 0) { return YES; } return NO; } - (void)tableView:(UITableView*)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath*)indexPath { // iOS8 requires this method to enable editing (see editActionsForRowAtIndexPath). } #pragma mark - UITableView delegate - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath; { cell.backgroundColor = ThemeService.shared.theme.backgroundColor; // Update the selected background view if (ThemeService.shared.theme.selectedBackgroundColor) { cell.selectedBackgroundView = [[UIView alloc] init]; cell.selectedBackgroundView.backgroundColor = ThemeService.shared.theme.selectedBackgroundColor; } else { if (tableView.style == UITableViewStylePlain) { cell.selectedBackgroundView = nil; } else { cell.selectedBackgroundView.backgroundColor = nil; } } } - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { CGFloat height = 0.0; if (_isAddParticipantSearchBarEditing) { height = [contactsDataSource heightForHeaderInSection:section]; } return height; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if (_isAddParticipantSearchBarEditing) { return [super tableView:tableView heightForRowAtIndexPath:indexPath]; } return 74; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (_isAddParticipantSearchBarEditing) { [super tableView:tableView didSelectRowAtIndexPath:indexPath]; } else { // Do nothing [tableView deselectRowAtIndexPath:indexPath animated:YES]; } } - (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath { NSMutableArray* actions; // add the swipe to delete only on participants sections if (indexPath.section == participantsSection && indexPath.row != 0) { actions = [[NSMutableArray alloc] init]; // Patch: Force the width of the button by adding whitespace characters into the title string. UITableViewRowAction *leaveAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@" " handler:^(UITableViewRowAction *action, NSIndexPath *indexPath){ [self onDeleteAt:indexPath]; }]; leaveAction.backgroundColor = [MXKTools convertImageToPatternColor:@"remove_icon" backgroundColor:ThemeService.shared.theme.headerBackgroundColor patternSize:CGSizeMake(74, 74) resourceSize:CGSizeMake(24, 24)]; [actions insertObject:leaveAction atIndex:0]; } return actions; } #pragma mark - Actions - (void)onDeleteAt:(NSIndexPath*)path { NSInteger row = path.row; row --; if (row < participants.count) { [participants removeObjectAtIndex:row]; [self refreshParticipants]; [self refreshContactsTable]; } } - (IBAction)onButtonPressed:(id)sender { if (sender == createBarButtonItem) { // Disable button to prevent multiple request createBarButtonItem.enabled = NO; [self startActivityIndicator]; // Prepare the invited participant data NSMutableArray *inviteArray = [NSMutableArray array]; NSMutableArray *invite3PIDArray = [NSMutableArray array]; // Check whether some users must be invited for (MXKContact *contact in participants) { NSArray *identifiers = contact.matrixIdentifiers; if (identifiers.count) { [inviteArray addObject:identifiers.firstObject]; } else { // This is a text entered by the user, or a local contact NSString *participantId; if (contact.emailAddresses.count) { // This is a local contact, consider the first email by default. // TODO: Prompt the user to select the right email. MXKEmail *email = contact.emailAddresses.firstObject; participantId = email.emailAddress; } else { // This is the text filled by the user. participantId = contact.displayName; } // Is it an email or a Matrix user ID? if ([MXTools isEmailAddress:participantId]) { // The identity server must be defined if (!self.mainSession.matrixRestClient.identityServer) { MXLogDebug(@"[StartChatViewController] Invite %@ failed", participantId); continue; } // The hostname of the identity server must not have the protocol part NSString *identityServer = self.mainSession.matrixRestClient.identityServer; if ([identityServer hasPrefix:@"http://"] || [identityServer hasPrefix:@"https://"]) { identityServer = [identityServer substringFromIndex:[identityServer rangeOfString:@"://"].location + 3]; } MXInvite3PID *invite3PID = [[MXInvite3PID alloc] init]; invite3PID.identityServer = identityServer; invite3PID.medium = kMX3PIDMediumEmail; invite3PID.address = participantId; [invite3PIDArray addObject:invite3PID]; } else { [inviteArray addObject:participantId]; } } } // Is it a direct chat? BOOL isDirect = ((inviteArray.count + invite3PIDArray.count == 1) ? YES : NO); // In case of a direct chat with only one user id, we open the first available direct chat // or creates a new one (if it doesn't exist). if (isDirect && inviteArray.count) { [[AppDelegate theDelegate] startDirectChatWithUserId:inviteArray.firstObject completion:^{ self->createBarButtonItem.enabled = YES; [self stopActivityIndicator]; }]; } else { // We don't want to create a new direct room for a 3rd party invite if we already have one NSString *first3rdPartyInvitee = invite3PIDArray.firstObject.address; if (isDirect && first3rdPartyInvitee) { MXRoom *existingRoom = [self.mainSession directJoinedRoomWithUserId:first3rdPartyInvitee]; if (existingRoom) { [self stopActivityIndicator]; [[AppDelegate theDelegate] showRoom:existingRoom.roomId andEventId:nil withMatrixSession:self.mainSession]; return; } } // Ensure direct chat are created with equal ops on both sides (the trusted_private_chat preset) MXRoomPreset preset = (isDirect ? kMXRoomPresetTrustedPrivateChat : nil); MXWeakify(self); void (^onFailure)(NSError *) = ^(NSError *error){ MXStrongifyAndReturnIfNil(self); self->createBarButtonItem.enabled = YES; self->roomCreationRequest = nil; [self stopActivityIndicator]; MXLogDebug(@"[StartChatViewController] Create room failed"); // Alert user [[AppDelegate theDelegate] showAlertWithTitle:nil message:[VectorL10n roomCreationDmError]]; }; [self.mainSession vc_canEnableE2EByDefaultInNewRoomWithUsers:inviteArray success:^(BOOL canEnableE2E) { MXStrongifyAndReturnIfNil(self); // Create new room MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters new]; roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPrivate; roomCreationParameters.inviteArray = inviteArray.count ? inviteArray : nil; roomCreationParameters.invite3PIDArray = invite3PIDArray.count ? invite3PIDArray : nil; roomCreationParameters.isDirect = isDirect; roomCreationParameters.preset = preset; if (canEnableE2E) { roomCreationParameters.initialStateEvents = @[ [MXRoomCreationParameters initialStateEventForEncryptionWithAlgorithm:kMXCryptoMegolmAlgorithm ]]; } self->roomCreationRequest = [self.mainSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) { // Update the room summary [room.summary resetRoomStateData]; self->roomCreationRequest = nil; [self stopActivityIndicator]; Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerCreated; [[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession]; } failure:onFailure]; } failure:onFailure]; } } else if (sender == self.navigationItem.leftBarButtonItem) { // Cancel has been pressed if (_isAddParticipantSearchBarEditing && participants.count) { // Cancel the search process [self searchBarCancelButtonClicked:_searchBarView]; } else { // Cancel the new chat creation [self.navigationController dismissViewControllerAnimated:YES completion:nil]; } } } #pragma mark - UISearchBar delegate - (void)refreshSearchBarItemsColor:(UISearchBar *)searchBar { // bar tint color searchBar.barTintColor = searchBar.tintColor = ThemeService.shared.theme.tintColor; // FIXME: this all seems incredibly fragile and tied to gutwrenching the current UISearchBar internals. // text color UITextField *searchBarTextField = searchBar.vc_searchTextField; searchBarTextField.textColor = ThemeService.shared.theme.textSecondaryColor; // Magnifying glass icon. UIImageView *leftImageView = (UIImageView *)searchBarTextField.leftView; leftImageView.image = [leftImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; leftImageView.tintColor = ThemeService.shared.theme.textSecondaryColor; // Use the theme's grey color. // The effect views are needed due to minimal style. // With default style there is a border above the search bar. searchBarTextField.backgroundColor = ThemeService.shared.theme.textQuinaryColor; UIView *effectBackgroundTop = [searchBarTextField valueForKey:@"_effectBackgroundTop"]; UIView *effectBackgroundBottom = [searchBarTextField valueForKey:@"_effectBackgroundBottom"]; effectBackgroundTop.hidden = YES; effectBackgroundBottom.hidden = YES; } - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { AccountRestrictionService *service = [[AccountRestrictionService alloc] initWithMxSession:self.mainSession]; if(![service isAddressListRestriction]) { [contactsDataSource searchWithPattern:searchText forceReset:NO]; } // $$$ 1.10.5 has the floowing lines // self->currentSearch = searchText; // if (searchText != nil && searchText.length > 0) // { // MXKContact *contact = nil; // if ([MXTools isMatrixUserIdentifier:searchText]) // { // contact = [[MXKContact alloc] initMatrixContactWithDisplayName:searchText andMatrixID:searchText]; // // } // else if ([MXTools isEmailAddress:searchText]) // { // contact = [[MXKContact alloc] initContactWithDisplayName:searchText emails:nil phoneNumbers:nil andThumbnail:nil]; // } // // [self showAllowOnlyOneInvitByEmailAllowedHeaderView: ![self canAddParticipant:contact]]; // } // else // { // [self showAllowOnlyOneInvitByEmailAllowedHeaderView:NO]; // } [contactsDataSource searchWithPattern:searchText forceReset:NO]; self.contactsAreFilteredWithSearch = searchText.length ? YES : NO; } - (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar { self.isAddParticipantSearchBarEditing = YES; searchBar.showsCancelButton = NO; return YES; } - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { searchBar.text = nil; self->currentSearch = nil; self.isAddParticipantSearchBarEditing = NO; // Reset filtering [contactsDataSource searchWithPattern:nil forceReset:NO]; // Leave search [searchBar resignFirstResponder]; [self showAllowOnlyOneInvitByEmailAllowedHeaderView:NO]; } #pragma mark - ContactsTableViewControllerDelegate - (void)contactsTableViewController:(ContactsTableViewController *)contactsTableViewController didSelectContact:(MXKContact*)contact { // If contact has only an email the identity server must be defined if (!self.mainSession.matrixRestClient.identityServer && contact.matrixIdentifiers.count == 0) { NSString *participantId; if (contact.emailAddresses.count) { MXKEmail *email = contact.emailAddresses.firstObject; participantId = email.emailAddress; } else { // This is the text filled by the user. participantId = contact.displayName; } if ([MXTools isEmailAddress:participantId]) { MXLogDebug(@"[StartChatViewController] No identity server is configured, do not add participant with email"); [contactsTableViewController refreshCurrentSelectedCell:YES]; UIAlertController *alert = [UIAlertController alertControllerWithTitle:[VectorL10n error] message:[VectorL10n roomCreationErrorInviteUserByEmailWithoutIdentityServer] preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok] style:UIAlertActionStyleDefault handler:nil]]; [self presentViewController:alert animated:YES completion:nil]; return; } } if ([self canAddParticipant:contact]) { // Update here the mutable list of participants [participants addObject:contact]; // Refresh display by leaving search session [self searchBarCancelButtonClicked:_searchBarView]; // bwi: if there is already at least one participant, the search bar should be hidden if (participants.count > 0) { [self hideSearchBar:true]; } } } #pragma mark - InviteFriendsHeaderViewDelegate - (void)inviteFriendsHeaderView:(InviteFriendsHeaderView *)headerView didTapButton:(UIButton *)button { [self showInviteFriendsFromSourceView:button]; } #pragma mark - bwi: hideSearchBar - (void)hideSearchBar:(BOOL)shouldHide { self.searchBarHeader.alpha = shouldHide ? 0.0f : 1.0f; self.searchBarHeaderHeightConstraint.constant = shouldHide ? 0.0f : 50.0f; [UIView animateWithDuration:0.2f animations:^{ [self.view layoutIfNeeded]; }]; } @end