/* * Copyright (c) 2010-2020 Belledonne Communications SARL. * * This file is part of linphone-iphone * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #import "ContactDetailsTableView.h" #import "PhoneMainView.h" #import "UIContactDetailsCell.h" #import "Utils.h" #import "OrderedDictionary.h" @implementation ContactDetailsTableView #pragma mark - Property Functions - (NSMutableArray *)getSectionData:(NSInteger)section { if (section == ContactSections_Number) { return _contact.phones; } else if (section == ContactSections_Sip) { return _contact.sipAddresses; } else if (section == ContactSections_Email) { if ([LinphoneManager.instance lpConfigBoolForKey:@"show_contacts_emails_preference"] == true) { return _contact.emails; } } return nil; } - (void)removeEmptyEntry:(UITableView *)tableview section:(NSInteger)section animated:(BOOL)animated { NSMutableArray *sectionDict = [self getSectionData:section]; for (NSInteger i = sectionDict.count - 1; i >= 0; i--) { NSString *value = sectionDict[i]; if (value.length == 0) { [self removeEntry:tableview indexPath:[NSIndexPath indexPathForRow:i inSection:section] animated:animated]; } } } - (void)removeEntry:(UITableView *)tableview indexPath:(NSIndexPath *)path animated:(BOOL)animated { bool rmed = YES; if (path.section == ContactSections_Number) { rmed = [_contact removePhoneNumberAtIndex:path.row]; } else if (path.section == ContactSections_Sip) { rmed = [_contact removeSipAddressAtIndex:path.row]; } else if (path.section == ContactSections_Email) { rmed = [_contact removeEmailAtIndex:path.row]; } else { rmed = NO; } if (rmed) { [tableview deleteRowsAtIndexPaths:@[ path ] withRowAnimation:animated ? UITableViewRowAnimationFade : UITableViewRowAnimationNone]; } else { LOGW(@"Cannot remove entry at path %@, skipping", path); } } - (void)addEntry:(UITableView *)tableview section:(NSInteger)section animated:(BOOL)animated value:(NSString *)value { bool added = FALSE; if (section == ContactSections_Number) { if ([_contact.phones count] == [_contact.person.phoneNumbers count]) added = [_contact addPhoneNumber:value]; } else if (section == ContactSections_Sip) { if ([_contact.sipAddresses count] == [self countSipAddressFromCNContact:_contact.person]) //[_contact.person.instantMessageAddresses count]) added = [_contact addSipAddress:value]; } else if (section == ContactSections_Email) { if ([_contact.emails count] == [_contact.person.emailAddresses count]) added = [_contact addEmail:value]; } if (added) { NSUInteger count = [self getSectionData:section].count; [tableview insertRowsAtIndexPaths:@[ [NSIndexPath indexPathForRow:count - 1 inSection:section] ] withRowAnimation:animated ? UITableViewRowAnimationFade : UITableViewRowAnimationNone]; } else { LOGW(@"Cannot add entry '%@' in section %d, skipping", value, section); } } -(NSInteger)countSipAddressFromCNContact:(CNContact*) mCNContact{ NSInteger count = 0; if (mCNContact.instantMessageAddresses != NULL) { for (CNLabeledValue *sipAddr in mCNContact.instantMessageAddresses) { if ([FastAddressBook isSipAddress:sipAddr]) count++; } } return count; } - (void)setContact:(Contact *)acontact { // if (acontact == _contact) // return; _contact = acontact; [self loadData]; } - (void)addPhoneField:(NSString *)number { ContactSections i = 0; while (i != ContactSections_MAX && i != ContactSections_Number) ++i; [self addEntry:[self tableView] section:i animated:FALSE value:number]; } - (void)addSipField:(NSString *)address { ContactSections i = 0; while (i != ContactSections_MAX && i != ContactSections_Sip) ++i; [self addEntry:[self tableView] section:i animated:FALSE value:address]; } - (void)addEmailField:(NSString *)address { ContactSections i = 0; while (i != ContactSections_MAX && i != ContactSections_Email) ++i; [self addEntry:[self tableView] section:i animated:FALSE value:address]; } - (BOOL)isValid { BOOL hasName = (_contact.firstName.length + _contact.lastName.length > 0); BOOL hasAddr = (_contact.phones.count + _contact.sipAddresses.count) > 0; return hasName && hasAddr; } #pragma mark - UITableViewDataSource Functions - (void)loadData { [self.tableView reloadData]; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return ContactSections_MAX; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { if (section == ContactSections_FirstName || section == ContactSections_LastName) { /*first and last name only when editting */ return (self.tableView.isEditing) ? 1 : 0; } else if (section == ContactSections_Sip) { return _contact.sipAddresses.count; } else if (section == ContactSections_Number) { return _contact.phones.count; } else if (section == ContactSections_Email) { BOOL showEmails = [LinphoneManager.instance lpConfigBoolForKey:@"show_contacts_emails_preference"]; return showEmails ? _contact.emails.count : 0; } return 0; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *kCellId = @"UIContactDetailsCell"; UIContactDetailsCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellId]; if (cell == nil) { cell = [[UIContactDetailsCell alloc] initWithIdentifier:kCellId]; cell.waitView = _waitView; [cell.editTextfield setDelegate:self]; } cell.selectionStyle = UITableViewCellSelectionStyleNone; cell.indexPath = indexPath; [cell hideDeleteButton:NO]; [cell.editTextfield setKeyboardType:UIKeyboardTypeDefault]; NSString *value = @""; if (indexPath.section == ContactSections_FirstName) { value = _contact.firstName; [cell hideDeleteButton:YES]; } else if (indexPath.section == ContactSections_LastName) { value = _contact.lastName; [cell hideDeleteButton:YES]; } else if ([indexPath section] == ContactSections_Number) { value = _contact.phones[indexPath.row]; [cell.editTextfield setKeyboardType:UIKeyboardTypePhonePad]; } else if ([indexPath section] == ContactSections_Sip) { value = _contact.sipAddresses[indexPath.row]; LinphoneAddress *addr = NULL; if ([LinphoneManager.instance lpConfigBoolForKey:@"contact_display_username_only"] && (addr = linphone_core_interpret_url(LC, [value UTF8String]))) { value = [NSString stringWithCString:linphone_address_get_username(addr) encoding:[NSString defaultCStringEncoding]]; linphone_address_destroy(addr); } [cell.editTextfield setKeyboardType:UIKeyboardTypeASCIICapable]; } else if ([indexPath section] == ContactSections_Email) { value = _contact.emails[indexPath.row]; [cell.editTextfield setKeyboardType:UIKeyboardTypeEmailAddress]; } if ([value hasPrefix:@" "]) value = [value substringFromIndex:1]; [cell setAddress:value]; cell.contentView.userInteractionEnabled = false; return cell; } - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { [LinphoneUtils findAndResignFirstResponder:[self tableView]]; if (editingStyle == UITableViewCellEditingStyleInsert) { [tableView beginUpdates]; [self addEntry:tableView section:[indexPath section] animated:TRUE value:@" "]; [tableView endUpdates]; } else if (editingStyle == UITableViewCellEditingStyleDelete) { [tableView beginUpdates]; [self removeEntry:tableView indexPath:indexPath animated:TRUE]; [tableView endUpdates]; } } #pragma mark - UITableViewDelegate Functions - (void)setEditing:(BOOL)editing animated:(BOOL)animated { BOOL showEmails = [LinphoneManager.instance lpConfigBoolForKey:@"show_contacts_emails_preference"]; if (editing) { // add phone/SIP/email entries so that the user can add new data for (int section = 0; section < [self numberOfSectionsInTableView:[self tableView]]; ++section) { if (section == ContactSections_Number || section == ContactSections_Sip || (showEmails && section == ContactSections_Email)) { [self addEntry:self.tableView section:section animated:animated value:@""]; } } _editButton.enabled = [self isValid]; } else { [LinphoneUtils findAndResignFirstResponder:[self tableView]]; // remove empty phone numbers for (int section = 0; section < [self numberOfSectionsInTableView:[self tableView]]; ++section) { // remove phony entries that were not filled by the user if (section == ContactSections_Number || section == ContactSections_Sip || (showEmails && section == ContactSections_Email)) { [self removeEmptyEntry:self.tableView section:section animated:NO]; } } _editButton.enabled = YES; } // order is imported here: empty rows must be deleted before table change editing mode [super setEditing:editing animated:animated]; [self loadData]; } - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { NSString *text = nil; BOOL canAddEntry = self.tableView.isEditing; NSString *addEntryName = nil; if (section == ContactSections_FirstName && self.tableView.isEditing) { text = NSLocalizedString(@"First name", nil); canAddEntry = NO; } else if (section == ContactSections_LastName && self.tableView.isEditing) { text = NSLocalizedString(@"Last name", nil); canAddEntry = NO; } else if ([self getSectionData:section].count > 0 || self.tableView.isEditing) { if (section == ContactSections_Number) { text = NSLocalizedString(@"Phone numbers", nil); addEntryName = NSLocalizedString(@"Add new phone number", nil); } else if (section == ContactSections_Sip) { text = NSLocalizedString(@"SIP addresses", nil); addEntryName = NSLocalizedString(@"Add new SIP address", nil); } else if (section == ContactSections_Email && [LinphoneManager.instance lpConfigBoolForKey:@"show_contacts_emails_preference"]) { text = NSLocalizedString(@"Email addresses", nil); addEntryName = NSLocalizedString(@"Add new email", nil); } } if (!text) { return nil; } CGRect frame = CGRectMake(0, 0, tableView.frame.size.width, 30); UIView *tempView = [[UIView alloc] initWithFrame:frame]; if (@available(iOS 13, *)) { tempView.backgroundColor = [UIColor systemBackgroundColor]; } else { tempView.backgroundColor = [UIColor whiteColor]; } UILabel *tempLabel = [[UILabel alloc] initWithFrame:frame]; tempLabel.backgroundColor = [UIColor clearColor]; tempLabel.textColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"color_E.png"]]; tempLabel.text = text.uppercaseString; tempLabel.textAlignment = NSTextAlignmentCenter; tempLabel.font = [UIFont systemFontOfSize:15]; tempLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; [tempView addSubview:tempLabel]; if (canAddEntry) { frame.origin.x = (tableView.frame.size.width - 30 /*image size*/) / 2 - 5 /*right offset*/; UIIconButton *tempAddButton = [[UIIconButton alloc] initWithFrame:frame]; [tempAddButton setImage:[UIImage imageNamed:@"add_field_default.png"] forState:UIControlStateNormal]; [tempAddButton setImage:[UIImage imageNamed:@"add_field_over.png"] forState:UIControlStateHighlighted]; [tempAddButton setImage:[UIImage imageNamed:@"add_field_over.png"] forState:UIControlStateSelected]; [tempAddButton addTarget:self action:@selector(onAddClick:) forControlEvents:UIControlEventTouchUpInside]; tempAddButton.tag = section; tempAddButton.accessibilityLabel = addEntryName; tempAddButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; [tempView addSubview:tempAddButton]; } return tempView; } - (void)onAddClick:(id)sender { NSInteger section = ((UIButton *)sender).tag; UITableView *tableView = VIEW(ContactDetailsView).tableController.tableView; NSInteger count = [self.tableView numberOfRowsInSection:section]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:count inSection:section]; [tableView.dataSource tableView:tableView commitEditingStyle:UITableViewCellEditingStyleInsert forRowAtIndexPath:indexPath]; } - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath { return UITableViewCellEditingStyleNone; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if (tableView.isEditing) { return 44; } else { return 88; } } - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { return 1e-5; } - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { if (section == 0 || (!self.tableView.isEditing && (section == ContactSections_FirstName || section == ContactSections_LastName))) { return 1e-5; } return [self tableView:tableView viewForHeaderInSection:section].frame.size.height; } #pragma mark - UITextFieldDelegate Functions - (BOOL)textFieldShouldReturn:(UITextField *)textField { [textField resignFirstResponder]; return YES; } - (void)textFieldUpdated:(UITextField *)textField { UIView *view = [textField superview]; while (view != nil && ![view isKindOfClass:[UIContactDetailsCell class]]) view = [view superview]; if (view != nil) { UIContactDetailsCell *cell = (UIContactDetailsCell *)view; // we cannot use indexPathForCell method here because if the cell is not visible anymore, // it will return nil.. NSIndexPath *path = [self.tableView indexPathForCell:cell]; // [self.tableView indexPathForCell:cell]; ContactSections sect = (ContactSections)[path section]; NSString *value = [textField text]; switch (sect) { case ContactSections_FirstName: _contact.firstName = value; break; case ContactSections_LastName: _contact.lastName = value; break; case ContactSections_Sip: [_contact setSipAddress:value atIndex:path.row]; value = _contact.sipAddresses[path.row]; // in case of reformatting break; case ContactSections_Email: [_contact setEmail:value atIndex:path.row]; value = _contact.emails[path.row]; // in case of reformatting break; case ContactSections_Number: [_contact setPhoneNumber:value atIndex:path.row]; value = _contact.phones[path.row]; // in case of // reformatting break; case ContactSections_MAX: case ContactSections_None: break; } cell.editTextfield.text = value; _editButton.enabled = [self isValid]; } } - (void)textFieldDidEndEditing:(UITextField *)textField { [self textFieldUpdated:textField]; // TODO reload current contact } - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { #if 0 // every time we modify contact entry, we must check if we can enable "edit" button UIView *view = [textField superview]; while (view != nil && ![view isKindOfClass:[UIContactDetailsCell class]]) view = [view superview]; UIContactDetailsCell *cell = (UIContactDetailsCell *)view; // we cannot use indexPathForCell method here because if the cell is not visible anymore, // it will return nil.. NSIndexPath *path = cell.indexPath; _editButton.enabled = NO; for (ContactSections s = ContactSections_Sip; !_editButton.enabled && s <= ContactSections_Number; s++) { for (int i = 0; !_editButton.enabled && i < [self tableView:self.tableView numberOfRowsInSection:s]; i++) { NSIndexPath *cellpath = [NSIndexPath indexPathForRow:i inSection:s]; if ([cellpath isEqual:path]) { _editButton.enabled = (textField.text.length > 0); } else { UIContactDetailsCell *cell = (UIContactDetailsCell *)[self tableView:self.tableView cellForRowAtIndexPath:cellpath]; _editButton.enabled = (cell.editTextfield.text.length > 0); } } } #else [self textFieldUpdated:textField]; #endif return YES; } @end