| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/browser/ui/settings/autofill_profile_table_view_controller.h" |
| |
| #include "base/logging.h" |
| #include "base/mac/foundation_util.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "components/autofill/core/browser/personal_data_manager.h" |
| #include "components/autofill/core/common/autofill_prefs.h" |
| #import "components/autofill/ios/browser/personal_data_manager_observer_bridge.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "ios/chrome/browser/application_context.h" |
| #include "ios/chrome/browser/autofill/personal_data_manager_factory.h" |
| #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| #import "ios/chrome/browser/ui/settings/autofill_profile_edit_table_view_controller.h" |
| #import "ios/chrome/browser/ui/settings/cells/autofill_data_item.h" |
| #import "ios/chrome/browser/ui/settings/cells/settings_switch_item.h" |
| #import "ios/chrome/browser/ui/settings/cells/settings_text_item.h" |
| #import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h" |
| #import "ios/chrome/browser/ui/table_view/cells/table_view_detail_text_item.h" |
| #import "ios/chrome/browser/ui/table_view/cells/table_view_link_header_footer_item.h" |
| #import "ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.h" |
| #import "ios/chrome/browser/ui/table_view/table_view_model.h" |
| #import "ios/chrome/browser/ui/util/uikit_ui_util.h" |
| #include "ios/chrome/grit/ios_strings.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| NSString* const kAutofillProfileTableViewID = @"kAutofillProfileTableViewID"; |
| |
| namespace { |
| |
| typedef NS_ENUM(NSInteger, SectionIdentifier) { |
| SectionIdentifierSwitches = kSectionIdentifierEnumZero, |
| SectionIdentifierProfiles, |
| }; |
| |
| typedef NS_ENUM(NSInteger, ItemType) { |
| ItemTypeAutofillAddressSwitch = kItemTypeEnumZero, |
| ItemTypeAddress, |
| ItemTypeHeader, |
| ItemTypeFooter, |
| }; |
| |
| } // namespace |
| |
| #pragma mark - AutofillProfileTableViewController |
| |
| @interface AutofillProfileTableViewController () <PersonalDataManagerObserver> { |
| autofill::PersonalDataManager* _personalDataManager; |
| |
| ios::ChromeBrowserState* _browserState; |
| std::unique_ptr<autofill::PersonalDataManagerObserverBridge> _observer; |
| |
| // Deleting profiles updates PersonalDataManager resulting in an observer |
| // callback, which handles general data updates with a reloadData. |
| // It is better to handle user-initiated changes with more specific actions |
| // such as inserting or removing items/sections. This boolean is used to |
| // stop the observer callback from acting on user-initiated changes. |
| BOOL _deletionInProgress; |
| } |
| |
| @property(nonatomic, getter=isAutofillProfileEnabled) |
| BOOL autofillProfileEnabled; |
| |
| @end |
| |
| @implementation AutofillProfileTableViewController |
| |
| - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState { |
| DCHECK(browserState); |
| self = |
| [super initWithTableViewStyle:UITableViewStyleGrouped |
| appBarStyle:ChromeTableViewControllerStyleWithAppBar]; |
| if (self) { |
| self.title = l10n_util::GetNSString(IDS_AUTOFILL_ADDRESSES_SETTINGS_TITLE); |
| self.shouldHideDoneButton = YES; |
| _browserState = browserState; |
| _personalDataManager = |
| autofill::PersonalDataManagerFactory::GetForBrowserState(_browserState); |
| _observer.reset(new autofill::PersonalDataManagerObserverBridge(self)); |
| _personalDataManager->AddObserver(_observer.get()); |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| _personalDataManager->RemoveObserver(_observer.get()); |
| } |
| |
| - (void)viewDidLoad { |
| [super viewDidLoad]; |
| self.tableView.allowsMultipleSelectionDuringEditing = YES; |
| self.tableView.accessibilityIdentifier = kAutofillProfileTableViewID; |
| self.tableView.estimatedSectionFooterHeight = |
| kTableViewHeaderFooterViewHeight; |
| [self updateEditButton]; |
| [self loadModel]; |
| } |
| |
| - (void)loadModel { |
| [super loadModel]; |
| TableViewModel* model = self.tableViewModel; |
| |
| [model addSectionWithIdentifier:SectionIdentifierSwitches]; |
| [model addItem:[self addressSwitchItem] |
| toSectionWithIdentifier:SectionIdentifierSwitches]; |
| [model setFooter:[self addressSwitchFooter] |
| forSectionWithIdentifier:SectionIdentifierSwitches]; |
| |
| [self populateProfileSection]; |
| } |
| |
| #pragma mark - LoadModel Helpers |
| |
| // Populates profile section using personalDataManager. |
| - (void)populateProfileSection { |
| TableViewModel* model = self.tableViewModel; |
| const std::vector<autofill::AutofillProfile*> autofillProfiles = |
| _personalDataManager->GetProfiles(); |
| if (!autofillProfiles.empty()) { |
| [model addSectionWithIdentifier:SectionIdentifierProfiles]; |
| [model setHeader:[self profileSectionHeader] |
| forSectionWithIdentifier:SectionIdentifierProfiles]; |
| for (autofill::AutofillProfile* autofillProfile : autofillProfiles) { |
| DCHECK(autofillProfile); |
| [model addItem:[self itemForProfile:*autofillProfile] |
| toSectionWithIdentifier:SectionIdentifierProfiles]; |
| } |
| } |
| } |
| |
| - (TableViewItem*)addressSwitchItem { |
| SettingsSwitchItem* switchItem = |
| [[SettingsSwitchItem alloc] initWithType:ItemTypeAutofillAddressSwitch]; |
| switchItem.text = |
| l10n_util::GetNSString(IDS_AUTOFILL_ENABLE_PROFILES_TOGGLE_LABEL); |
| switchItem.on = [self isAutofillProfileEnabled]; |
| switchItem.accessibilityIdentifier = @"addressItem_switch"; |
| return switchItem; |
| } |
| |
| - (TableViewHeaderFooterItem*)addressSwitchFooter { |
| TableViewLinkHeaderFooterItem* footer = |
| [[TableViewLinkHeaderFooterItem alloc] initWithType:ItemTypeFooter]; |
| footer.text = |
| l10n_util::GetNSString(IDS_AUTOFILL_ENABLE_PROFILES_TOGGLE_SUBLABEL); |
| return footer; |
| } |
| |
| - (TableViewHeaderFooterItem*)profileSectionHeader { |
| TableViewTextHeaderFooterItem* header = |
| [[TableViewTextHeaderFooterItem alloc] initWithType:ItemTypeHeader]; |
| header.text = l10n_util::GetNSString(IDS_AUTOFILL_ADDRESSES); |
| return header; |
| } |
| |
| - (TableViewItem*)itemForProfile: |
| (const autofill::AutofillProfile&)autofillProfile { |
| std::string guid(autofillProfile.guid()); |
| NSString* title = base::SysUTF16ToNSString( |
| autofillProfile.GetInfo(autofill::AutofillType(autofill::NAME_FULL), |
| GetApplicationContext()->GetApplicationLocale())); |
| NSString* subTitle = base::SysUTF16ToNSString(autofillProfile.GetInfo( |
| autofill::AutofillType(autofill::ADDRESS_HOME_LINE1), |
| GetApplicationContext()->GetApplicationLocale())); |
| bool isServerProfile = autofillProfile.record_type() == |
| autofill::AutofillProfile::SERVER_PROFILE; |
| |
| AutofillDataItem* item = |
| [[AutofillDataItem alloc] initWithType:ItemTypeAddress]; |
| item.text = title; |
| item.leadingDetailText = subTitle; |
| item.accessoryType = UITableViewCellAccessoryDisclosureIndicator; |
| item.accessibilityIdentifier = title; |
| item.GUID = guid; |
| item.deletable = !isServerProfile; |
| if (isServerProfile) { |
| item.trailingDetailText = |
| l10n_util::GetNSString(IDS_IOS_AUTOFILL_WALLET_SERVER_NAME); |
| } |
| return item; |
| } |
| |
| - (BOOL)localProfilesExist { |
| return !_personalDataManager->GetProfiles().empty(); |
| } |
| |
| #pragma mark - SettingsRootTableViewController |
| |
| - (BOOL)shouldShowEditButton { |
| return YES; |
| } |
| |
| - (BOOL)editButtonEnabled { |
| return [self localProfilesExist]; |
| } |
| |
| - (void)editButtonPressed { |
| [super editButtonPressed]; |
| [self setSwitchItemEnabled:!self.tableView.editing |
| itemType:ItemTypeAutofillAddressSwitch]; |
| } |
| |
| // Override. |
| - (void)deleteItems:(NSArray<NSIndexPath*>*)indexPaths { |
| // If there are no index paths, return early. This can happen if the user |
| // presses the Delete button twice in quick succession. |
| if (![indexPaths count]) |
| return; |
| |
| _deletionInProgress = YES; |
| [self willDeleteItemsAtIndexPaths:indexPaths]; |
| // Call super to delete the items in the table view. |
| [super deleteItems:indexPaths]; |
| |
| // TODO(crbug.com/650390) Generalize removing empty sections |
| [self removeSectionIfEmptyForSectionWithIdentifier:SectionIdentifierProfiles]; |
| } |
| |
| #pragma mark - UITableViewDelegate |
| |
| - (void)tableView:(UITableView*)tableView |
| didSelectRowAtIndexPath:(NSIndexPath*)indexPath { |
| [super tableView:tableView didSelectRowAtIndexPath:indexPath]; |
| |
| // Edit mode is the state where the user can select and delete entries. In |
| // edit mode, selection is handled by the superclass. When not in edit mode |
| // selection presents the editing controller for the selected entry. |
| if ([self.tableView isEditing]) { |
| return; |
| } |
| |
| TableViewModel* model = self.tableViewModel; |
| if ([model itemTypeForIndexPath:indexPath] != ItemTypeAddress) |
| return; |
| |
| const std::vector<autofill::AutofillProfile*> autofillProfiles = |
| _personalDataManager->GetProfiles(); |
| AutofillProfileEditTableViewController* controller = |
| [AutofillProfileEditTableViewController |
| controllerWithProfile:*autofillProfiles[indexPath.item] |
| personalDataManager:_personalDataManager]; |
| controller.dispatcher = self.dispatcher; |
| [self.navigationController pushViewController:controller animated:YES]; |
| [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; |
| } |
| |
| #pragma mark - UITableViewDataSource |
| |
| - (BOOL)tableView:(UITableView*)tableView |
| canEditRowAtIndexPath:(NSIndexPath*)indexPath { |
| // Only autofill data cells are editable. |
| TableViewItem* item = [self.tableViewModel itemAtIndexPath:indexPath]; |
| if ([item isKindOfClass:[AutofillDataItem class]]) { |
| AutofillDataItem* autofillItem = |
| base::mac::ObjCCastStrict<AutofillDataItem>(item); |
| return [autofillItem isDeletable]; |
| } |
| return NO; |
| } |
| |
| - (void)tableView:(UITableView*)tableView |
| commitEditingStyle:(UITableViewCellEditingStyle)editingStyle |
| forRowAtIndexPath:(NSIndexPath*)indexPath { |
| if (editingStyle != UITableViewCellEditingStyleDelete) |
| return; |
| [self deleteItems:@[ indexPath ]]; |
| } |
| |
| - (UITableViewCell*)tableView:(UITableView*)tableView |
| cellForRowAtIndexPath:(NSIndexPath*)indexPath { |
| UITableViewCell* cell = [super tableView:tableView |
| cellForRowAtIndexPath:indexPath]; |
| |
| ItemType itemType = static_cast<ItemType>( |
| [self.tableViewModel itemTypeForIndexPath:indexPath]); |
| if (itemType == ItemTypeAutofillAddressSwitch) { |
| SettingsSwitchCell* switchCell = |
| base::mac::ObjCCastStrict<SettingsSwitchCell>(cell); |
| [switchCell.switchView addTarget:self |
| action:@selector(autofillAddressSwitchChanged:) |
| forControlEvents:UIControlEventValueChanged]; |
| } |
| |
| return cell; |
| } |
| |
| #pragma mark - Switch Callbacks |
| |
| - (void)autofillAddressSwitchChanged:(UISwitch*)switchView { |
| [self setSwitchItemOn:[switchView isOn] |
| itemType:ItemTypeAutofillAddressSwitch]; |
| [self setAutofillProfileEnabled:[switchView isOn]]; |
| } |
| |
| #pragma mark - Switch Helpers |
| |
| // Sets switchItem's state to |on|. It is important that there is only one item |
| // of |switchItemType| in SectionIdentifierSwitches. |
| - (void)setSwitchItemOn:(BOOL)on itemType:(ItemType)switchItemType { |
| NSIndexPath* switchPath = |
| [self.tableViewModel indexPathForItemType:switchItemType |
| sectionIdentifier:SectionIdentifierSwitches]; |
| SettingsSwitchItem* switchItem = |
| base::mac::ObjCCastStrict<SettingsSwitchItem>( |
| [self.tableViewModel itemAtIndexPath:switchPath]); |
| switchItem.on = on; |
| } |
| |
| // Sets switchItem's enabled status to |enabled| and reconfigures the |
| // corresponding cell. It is important that there is no more than one item of |
| // |switchItemType| in SectionIdentifierSwitches. |
| - (void)setSwitchItemEnabled:(BOOL)enabled itemType:(ItemType)switchItemType { |
| TableViewModel* model = self.tableViewModel; |
| |
| if (![model hasItemForItemType:switchItemType |
| sectionIdentifier:SectionIdentifierSwitches]) { |
| return; |
| } |
| NSIndexPath* switchPath = |
| [model indexPathForItemType:switchItemType |
| sectionIdentifier:SectionIdentifierSwitches]; |
| SettingsSwitchItem* switchItem = |
| base::mac::ObjCCastStrict<SettingsSwitchItem>( |
| [model itemAtIndexPath:switchPath]); |
| [switchItem setEnabled:enabled]; |
| [self reconfigureCellsForItems:@[ switchItem ]]; |
| } |
| |
| #pragma mark - PersonalDataManagerObserver |
| |
| - (void)onPersonalDataChanged { |
| if (_deletionInProgress) |
| return; |
| |
| if ([self.tableView isEditing]) { |
| // Turn off edit mode. |
| [self setEditing:NO animated:NO]; |
| } |
| |
| [self updateEditButton]; |
| [self reloadData]; |
| } |
| |
| #pragma mark - Getters and Setter |
| |
| - (BOOL)isAutofillProfileEnabled { |
| return autofill::prefs::IsProfileAutofillEnabled(_browserState->GetPrefs()); |
| } |
| |
| - (void)setAutofillProfileEnabled:(BOOL)isEnabled { |
| return autofill::prefs::SetProfileAutofillEnabled(_browserState->GetPrefs(), |
| isEnabled); |
| } |
| |
| #pragma mark - Private |
| |
| // Removes the item from the personal data manager model. |
| - (void)willDeleteItemsAtIndexPaths:(NSArray*)indexPaths { |
| for (NSIndexPath* indexPath in indexPaths) { |
| AutofillDataItem* item = base::mac::ObjCCastStrict<AutofillDataItem>( |
| [self.tableViewModel itemAtIndexPath:indexPath]); |
| _personalDataManager->RemoveByGUID([item GUID]); |
| } |
| } |
| |
| // Remove the section from the model and collectionView if there are no more |
| // items in the section. |
| - (void)removeSectionIfEmptyForSectionWithIdentifier: |
| (SectionIdentifier)sectionIdentifier { |
| if (![self.tableViewModel hasSectionForSectionIdentifier:sectionIdentifier]) { |
| _deletionInProgress = NO; |
| return; |
| } |
| NSInteger section = |
| [self.tableViewModel sectionForSectionIdentifier:sectionIdentifier]; |
| if ([self.tableView numberOfRowsInSection:section] == 0) { |
| // Avoid reference cycle in block. |
| __weak AutofillProfileTableViewController* weakSelf = self; |
| [self.tableView |
| performBatchUpdates:^{ |
| // Obtain strong reference again. |
| AutofillProfileTableViewController* strongSelf = weakSelf; |
| if (!strongSelf) { |
| return; |
| } |
| |
| // Remove section from model and collectionView. |
| [[strongSelf tableViewModel] |
| removeSectionWithIdentifier:sectionIdentifier]; |
| [[strongSelf tableView] |
| deleteSections:[NSIndexSet indexSetWithIndex:section] |
| withRowAnimation:UITableViewRowAnimationAutomatic]; |
| } |
| completion:^(BOOL finished) { |
| // Obtain strong reference again. |
| AutofillProfileTableViewController* strongSelf = weakSelf; |
| if (!strongSelf) { |
| return; |
| } |
| |
| // Turn off edit mode if there is nothing to edit. |
| if (![strongSelf localProfilesExist] && |
| [strongSelf.tableView isEditing]) { |
| [strongSelf setEditing:NO animated:YES]; |
| } |
| [strongSelf updateEditButton]; |
| strongSelf->_deletionInProgress = NO; |
| }]; |
| } else { |
| _deletionInProgress = NO; |
| } |
| } |
| |
| @end |