Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(101)

Unified Diff: ios/chrome/browser/ui/settings/settings_collection_view_controller.mm

Issue 2587023002: Upstream Chrome on iOS source code [8/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ios/chrome/browser/ui/settings/settings_collection_view_controller.mm
diff --git a/ios/chrome/browser/ui/settings/settings_collection_view_controller.mm b/ios/chrome/browser/ui/settings/settings_collection_view_controller.mm
new file mode 100644
index 0000000000000000000000000000000000000000..2628a0bdea123a3f3df4b743bc17300813464967
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/settings_collection_view_controller.mm
@@ -0,0 +1,1096 @@
+// 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/settings_collection_view_controller.h"
+
+#include <memory>
+
+#import "base/ios/weak_nsobject.h"
+#import "base/mac/foundation_util.h"
+#import "base/mac/scoped_nsobject.h"
+#include "base/metrics/user_metrics.h"
+#include "base/scoped_observer.h"
+#include "base/strings/sys_string_conversions.h"
+#include "components/autofill/core/common/autofill_pref_names.h"
+#include "components/browser_sync/profile_sync_service.h"
+#include "components/keyed_service/core/service_access_type.h"
+#include "components/password_manager/core/browser/password_bubble_experiment.h"
+#include "components/password_manager/core/browser/password_store.h"
+#include "components/password_manager/core/common/password_manager_pref_names.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_service.h"
+#include "components/search_engines/util.h"
+#include "components/signin/core/browser/signin_manager.h"
+#include "components/signin/core/common/signin_pref_names.h"
+#include "components/strings/grit/components_strings.h"
+#include "ios/chrome/browser/application_context.h"
+#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state_removal_controller.h"
+#include "ios/chrome/browser/experimental_flags.h"
+#include "ios/chrome/browser/passwords/ios_chrome_password_store_factory.h"
+#include "ios/chrome/browser/pref_names.h"
+#import "ios/chrome/browser/prefs/pref_observer_bridge.h"
+#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
+#import "ios/chrome/browser/signin/authentication_service.h"
+#include "ios/chrome/browser/signin/authentication_service_factory.h"
+#include "ios/chrome/browser/signin/chrome_identity_service_observer_bridge.h"
+#include "ios/chrome/browser/signin/signin_manager_factory.h"
+#include "ios/chrome/browser/sync/ios_chrome_profile_sync_service_factory.h"
+#include "ios/chrome/browser/sync/sync_observer_bridge.h"
+#include "ios/chrome/browser/sync/sync_setup_service.h"
+#include "ios/chrome/browser/sync/sync_setup_service_factory.h"
+#import "ios/chrome/browser/ui/authentication/signin_interaction_controller.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_account_item.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_detail_item.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_switch_item.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_text_item.h"
+#import "ios/chrome/browser/ui/collection_view/collection_view_model.h"
+#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
+#import "ios/chrome/browser/ui/settings/about_chrome_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/accounts_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/autofill_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/bandwidth_management_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/cells/account_signin_item.h"
+#import "ios/chrome/browser/ui/settings/content_settings_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/material_cell_catalog_view_controller.h"
+#import "ios/chrome/browser/ui/settings/native_apps_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/privacy_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/save_passwords_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/search_engine_settings_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/settings_utils.h"
+#import "ios/chrome/browser/ui/settings/utils/pref_backed_boolean.h"
+#import "ios/chrome/browser/ui/settings/voicesearch_collection_view_controller.h"
+#import "ios/chrome/browser/ui/sync/sync_util.h"
+#import "ios/chrome/browser/ui/uikit_ui_util.h"
+#include "ios/chrome/browser/voice/speech_input_locale_config.h"
+#include "ios/chrome/grit/ios_chromium_strings.h"
+#include "ios/chrome/grit/ios_strings.h"
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
+#import "ios/public/provider/chrome/browser/signin/chrome_identity.h"
+#include "ios/public/provider/chrome/browser/signin/signin_resources_provider.h"
+#include "ios/public/provider/chrome/browser/voice/voice_search_prefs.h"
+#include "ui/base/l10n/l10n_util_mac.h"
+
+NSString* const kSettingsCollectionViewId = @"kSettingsCollectionViewId";
+NSString* const kSettingsSignInCellId = @"kSettingsSignInCellId";
+NSString* const kSettingsAccountCellId = @"kSettingsAccountCellId";
+NSString* const kSettingsSearchEngineCellId = @"Search Engine";
+NSString* const kSettingsVoiceSearchCellId = @"Voice Search Settings";
+
+@interface SettingsCollectionViewController (NotificationBridgeDelegate)
+// Notifies this controller that the sign in state has changed.
+- (void)onSignInStateChanged;
+@end
+
+namespace {
+
+const CGFloat kAccountProfilePhotoDimension = 40.0f;
+
+typedef NS_ENUM(NSInteger, SectionIdentifier) {
+ SectionIdentifierSignIn = kSectionIdentifierEnumZero,
+ SectionIdentifierBasics,
+ SectionIdentifierAdvanced,
+ SectionIdentifierInfo,
+ SectionIdentifierDebug,
+};
+
+typedef NS_ENUM(NSInteger, ItemType) {
+ ItemTypeSignInButton = kItemTypeEnumZero,
+ ItemTypeAccount,
+ ItemTypeHeader,
+ ItemTypeSearchEngine,
+ ItemTypeSavedPasswords,
+ ItemTypeAutofill,
+ ItemTypeNativeApps,
+ ItemTypeVoiceSearch,
+ ItemTypePrivacy,
+ ItemTypeContentSettings,
+ ItemTypeBandwidth,
+ ItemTypeAboutChrome,
+ ItemTypeMemoryDebugging,
+ ItemTypeViewSource,
+ ItemTypeLogJavascript,
+ ItemTypeShowAutofillTypePredictions,
+ ItemTypeCellCatalog,
+};
+
+#if CHROMIUM_BUILD && !defined(NDEBUG)
+NSString* kDevViewSourceKey = @"DevViewSource";
+NSString* kLogJavascriptKey = @"LogJavascript";
+NSString* kShowAutofillTypePredictionsKey = @"ShowAutofillTypePredictions";
+#endif // CHROMIUM_BUILD && !defined(NDEBUG)
+
+#pragma mark - SigninObserverBridge Class
+
+class SigninObserverBridge : public SigninManagerBase::Observer {
+ public:
+ SigninObserverBridge(ios::ChromeBrowserState* browserState,
+ SettingsCollectionViewController* owner);
+ ~SigninObserverBridge() override{};
+
+ // SigninManagerBase::Observer implementation:
+ void GoogleSigninSucceeded(const std::string& account_id,
+ const std::string& username,
+ const std::string& password) override;
+ void GoogleSignedOut(const std::string& account_id,
+ const std::string& username) override;
+
+ private:
+ base::WeakNSObject<SettingsCollectionViewController> owner_;
+ ScopedObserver<SigninManager, SigninObserverBridge> observer_;
+};
+
+SigninObserverBridge::SigninObserverBridge(
+ ios::ChromeBrowserState* browserState,
+ SettingsCollectionViewController* owner)
+ : owner_(owner), observer_(this) {
+ DCHECK(owner_);
+ SigninManager* sigin_manager =
+ ios::SigninManagerFactory::GetForBrowserState(browserState);
+ if (!sigin_manager)
+ return;
+ observer_.Add(sigin_manager);
+}
+
+void SigninObserverBridge::GoogleSigninSucceeded(const std::string& account_id,
+ const std::string& username,
+ const std::string& password) {
+ [owner_ onSignInStateChanged];
+}
+
+void SigninObserverBridge::GoogleSignedOut(const std::string& account_id,
+ const std::string& username) {
+ [owner_ onSignInStateChanged];
+}
+
+} // namespace
+
+#pragma mark - SettingsCollectionViewController
+
+@interface SettingsCollectionViewController ()<SettingsControllerProtocol,
+ SyncObserverModelBridge,
+ ChromeIdentityServiceObserver,
+ BooleanObserver,
+ PrefObserverDelegate> {
+ // The main browser state that hold the settings. Never off the record.
+ ios::ChromeBrowserState* _mainBrowserState; // weak
+
+ // The current browser state. It is either |_mainBrowserState|
+ // or |_mainBrowserState->GetOffTheRecordChromeBrowserState()|.
+ ios::ChromeBrowserState* _currentBrowserState; // weak
+ std::unique_ptr<SigninObserverBridge> _notificationBridge;
+ std::unique_ptr<SyncObserverBridge> _syncObserverBridge;
+ base::scoped_nsobject<SigninInteractionController>
+ _signinInteractionController;
+ // Whether the impression of the Signin button has already been recorded.
+ BOOL _hasRecordedSigninImpression;
+ // PrefBackedBoolean for ShowMemoryDebugTools switch.
+ base::scoped_nsobject<PrefBackedBoolean> _showMemoryDebugToolsEnabled;
+ // The item related to the switch for the show suggestions setting.
+ base::scoped_nsobject<CollectionViewSwitchItem> _showMemoryDebugToolsItem;
+
+ // Cached resized profile image.
+ base::scoped_nsobject<UIImage> _resizedImage;
+ base::WeakNSObject<UIImage> _oldImage;
+
+ // Identity object and observer used for Account Item refresh.
+ base::scoped_nsobject<ChromeIdentity> _identity;
+ std::unique_ptr<ChromeIdentityServiceObserverBridge> _identityServiceObserver;
+
+ // PrefMember for voice locale code.
+ StringPrefMember _voiceLocaleCode;
+ // Pref observer to track changes to prefs.
+ std::unique_ptr<PrefObserverBridge> _prefObserverBridge;
+ // TODO(crbug.com/662435): Refactor PrefObserverBridge so it owns the
+ // PrefChangeRegistrar.
+ // Registrar for pref changes notifications.
+ PrefChangeRegistrar _prefChangeRegistrar;
+
+ // Updatable Items.
+ base::scoped_nsobject<CollectionViewDetailItem> _voiceSearchDetailItem;
+ base::scoped_nsobject<CollectionViewDetailItem> _defaultSearchEngineItem;
+ base::scoped_nsobject<CollectionViewDetailItem> _savePasswordsDetailItem;
+ base::scoped_nsobject<CollectionViewDetailItem> _autoFillDetailItem;
+}
+
+// Stops observing browser state services. This is required during the shutdown
+// phase to avoid observing services for a profile that is being killed.
+- (void)stopBrowserStateServiceObservers;
+
+@end
+
+@implementation SettingsCollectionViewController
+
+#pragma mark Initialization
+
+- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)mainBrowserState
+ currentBrowserState:
+ (ios::ChromeBrowserState*)currentBrowserState {
+ DCHECK(mainBrowserState);
+ DCHECK(currentBrowserState);
+ DCHECK_EQ(mainBrowserState,
+ currentBrowserState->GetOriginalChromeBrowserState());
+ self = [super initWithStyle:CollectionViewControllerStyleAppBar];
+ if (self) {
+ _mainBrowserState = mainBrowserState;
+ _currentBrowserState = currentBrowserState;
+ self.title = l10n_util::GetNSStringWithFixup(IDS_IOS_SETTINGS_TITLE);
+ self.collectionViewAccessibilityIdentifier = kSettingsCollectionViewId;
+ _notificationBridge.reset(
+ new SigninObserverBridge(_mainBrowserState, self));
+ syncer::SyncService* syncService =
+ IOSChromeProfileSyncServiceFactory::GetForBrowserState(
+ _mainBrowserState);
+ _syncObserverBridge.reset(new SyncObserverBridge(self, syncService));
+
+ _showMemoryDebugToolsEnabled.reset([[PrefBackedBoolean alloc]
+ initWithPrefService:GetApplicationContext()->GetLocalState()
+ prefName:prefs::kShowMemoryDebuggingTools]);
+ [_showMemoryDebugToolsEnabled setObserver:self];
+
+ AuthenticationService* authService =
+ AuthenticationServiceFactory::GetForBrowserState(_mainBrowserState);
+ _identity.reset([authService->GetAuthenticatedIdentity() retain]);
+ _identityServiceObserver.reset(
+ new ChromeIdentityServiceObserverBridge(self));
+
+ PrefService* prefService = _mainBrowserState->GetPrefs();
+
+ _voiceLocaleCode.Init(prefs::kVoiceSearchLocale, prefService);
+
+ _prefChangeRegistrar.Init(prefService);
+ _prefObserverBridge.reset(new PrefObserverBridge(self));
+ // Register to observe any changes on Perf backed values displayed by the
+ // screen.
+ _prefObserverBridge->ObserveChangesForPreference(prefs::kVoiceSearchLocale,
+ &_prefChangeRegistrar);
+ _prefObserverBridge->ObserveChangesForPreference(
+ password_manager::prefs::kPasswordManagerSavingEnabled,
+ &_prefChangeRegistrar);
+ _prefObserverBridge->ObserveChangesForPreference(
+ autofill::prefs::kAutofillEnabled, &_prefChangeRegistrar);
+
+ [self loadModel];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [self stopBrowserStateServiceObservers];
+ [super dealloc];
+}
+
+- (void)stopBrowserStateServiceObservers {
+ _syncObserverBridge.reset();
+ _notificationBridge.reset();
+ _identityServiceObserver.reset();
+ [_showMemoryDebugToolsEnabled setObserver:nil];
+}
+
+- (SigninInteractionController*)signinInteractionController {
+ return _signinInteractionController;
+}
+
+#pragma mark View lifecycle
+
+// TODO(crbug.com/661915): Refactor TemplateURLObserver and re-implement this so
+// it observes the default search engine name instead of reloading on
+// ViewWillAppear.
+- (void)viewWillAppear:(BOOL)animated {
+ [super viewWillAppear:animated];
+ [self updateSearchCell];
+}
+
+#pragma mark SettingsRootCollectionViewController
+
+- (void)loadModel {
+ [super loadModel];
+
+ CollectionViewModel* model = self.collectionViewModel;
+
+ // Sign in/Account section
+ [model addSectionWithIdentifier:SectionIdentifierSignIn];
+ AuthenticationService* authService =
+ AuthenticationServiceFactory::GetForBrowserState(_mainBrowserState);
+ if (!authService->IsAuthenticated()) {
+ if (!_hasRecordedSigninImpression) {
+ // Once the Settings are open, this button impression will at most be
+ // recorded once until they are closed.
+ base::RecordAction(
+ base::UserMetricsAction("Signin_Impression_FromSettings"));
+ _hasRecordedSigninImpression = YES;
+ }
+ [model addItem:[self signInTextItem]
+ toSectionWithIdentifier:SectionIdentifierSignIn];
+ } else {
+ [model addItem:[self accountCellItem]
+ toSectionWithIdentifier:SectionIdentifierSignIn];
+ }
+
+ // Basics section
+ [model addSectionWithIdentifier:SectionIdentifierBasics];
+ CollectionViewTextItem* basicsHeader = [
+ [[CollectionViewTextItem alloc] initWithType:ItemTypeHeader] autorelease];
+ basicsHeader.text = l10n_util::GetNSString(IDS_IOS_OPTIONS_GENERAL_TAB_LABEL);
+ [model setHeader:basicsHeader
+ forSectionWithIdentifier:SectionIdentifierBasics];
+ [model addItem:[self searchEngineDetailItem]
+ toSectionWithIdentifier:SectionIdentifierBasics];
+ [model addItem:[self savePasswordsDetailItem]
+ toSectionWithIdentifier:SectionIdentifierBasics];
+ [model addItem:[self autoFillDetailItem]
+ toSectionWithIdentifier:SectionIdentifierBasics];
+ [model addItem:[self nativeAppsDetailItem]
+ toSectionWithIdentifier:SectionIdentifierBasics];
+
+ // Advanced Section
+ [model addSectionWithIdentifier:SectionIdentifierAdvanced];
+ CollectionViewTextItem* advancedHeader = [
+ [[CollectionViewTextItem alloc] initWithType:ItemTypeHeader] autorelease];
+ advancedHeader.text =
+ l10n_util::GetNSString(IDS_IOS_OPTIONS_ADVANCED_TAB_LABEL);
+ [model setHeader:advancedHeader
+ forSectionWithIdentifier:SectionIdentifierAdvanced];
+ [model addItem:[self voiceSearchDetailItem]
+ toSectionWithIdentifier:SectionIdentifierAdvanced];
+ [model addItem:[self privacyDetailItem]
+ toSectionWithIdentifier:SectionIdentifierAdvanced];
+ [model addItem:[self contentSettingsDetailItem]
+ toSectionWithIdentifier:SectionIdentifierAdvanced];
+ [model addItem:[self bandwidthManagementDetailItem]
+ toSectionWithIdentifier:SectionIdentifierAdvanced];
+
+ // Info Section
+ [model addSectionWithIdentifier:SectionIdentifierInfo];
+ [model addItem:[self aboutChromeDetailItem]
+ toSectionWithIdentifier:SectionIdentifierInfo];
+
+ // Debug Section
+ if ([self hasDebugSection]) {
+ [model addSectionWithIdentifier:SectionIdentifierDebug];
+ CollectionViewTextItem* debugHeader = [[[CollectionViewTextItem alloc]
+ initWithType:ItemTypeHeader] autorelease];
+ debugHeader.text = @"Debug";
+ [model setHeader:debugHeader
+ forSectionWithIdentifier:SectionIdentifierDebug];
+ }
+
+ if (experimental_flags::IsMemoryDebuggingEnabled()) {
+ _showMemoryDebugToolsItem.reset([[self showMemoryDebugSwitchItem] retain]);
+ [model addItem:_showMemoryDebugToolsItem
+ toSectionWithIdentifier:SectionIdentifierDebug];
+ }
+
+#if CHROMIUM_BUILD && !defined(NDEBUG)
+ [model addItem:[self viewSourceSwitchItem]
+ toSectionWithIdentifier:SectionIdentifierDebug];
+ [model addItem:[self logJavascriptConsoleSwitchItem]
+ toSectionWithIdentifier:SectionIdentifierDebug];
+ [model addItem:[self showAutofillTypePredictionsSwitchItem]
+ toSectionWithIdentifier:SectionIdentifierDebug];
+ [model addItem:[self materialCatalogDetailItem]
+ toSectionWithIdentifier:SectionIdentifierDebug];
+#endif // CHROMIUM_BUILD && !defined(NDEBUG)
+}
+
+#pragma mark - Model Items
+
+- (CollectionViewItem*)signInTextItem {
+ AccountSignInItem* signInTextItem = [[[AccountSignInItem alloc]
+ initWithType:ItemTypeSignInButton] autorelease];
+ signInTextItem.accessibilityIdentifier = kSettingsSignInCellId;
+ UIImage* image = CircularImageFromImage(ios::GetChromeBrowserProvider()
+ ->GetSigninResourcesProvider()
+ ->GetDefaultAvatar(),
+ kAccountProfilePhotoDimension);
+ signInTextItem.image = image;
+
+ return signInTextItem;
+}
+
+- (CollectionViewItem*)accountCellItem {
+ CollectionViewAccountItem* identityAccountItem =
+ [[[CollectionViewAccountItem alloc] initWithType:ItemTypeAccount]
+ autorelease];
+ identityAccountItem.accessoryType =
+ MDCCollectionViewCellAccessoryDisclosureIndicator;
+ identityAccountItem.accessibilityIdentifier = kSettingsAccountCellId;
+ [self updateIdentityAccountItem:identityAccountItem];
+ return identityAccountItem;
+}
+
+- (CollectionViewItem*)searchEngineDetailItem {
+ NSString* defaultSearchEngineName =
+ base::SysUTF16ToNSString(GetDefaultSearchEngineName(
+ ios::TemplateURLServiceFactory::GetForBrowserState(
+ _mainBrowserState)));
+
+ _defaultSearchEngineItem.reset(
+ [[self detailItemWithType:ItemTypeSearchEngine
+ text:l10n_util::GetNSString(
+ IDS_IOS_SEARCH_ENGINE_SETTING_TITLE)
+ detailText:defaultSearchEngineName] retain]);
+ _defaultSearchEngineItem.get().accessibilityIdentifier =
+ kSettingsSearchEngineCellId;
+ return _defaultSearchEngineItem;
+}
+
+- (CollectionViewItem*)savePasswordsDetailItem {
+ BOOL savePasswordsEnabled = _mainBrowserState->GetPrefs()->GetBoolean(
+ password_manager::prefs::kPasswordManagerSavingEnabled);
+ NSString* passwordsDetail = savePasswordsEnabled
+ ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
+ : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
+ int titleId = IDS_IOS_SAVE_PASSWORDS;
+ syncer::SyncService* syncService =
+ IOSChromeProfileSyncServiceFactory::GetForBrowserState(_mainBrowserState);
+ if (password_bubble_experiment::IsSmartLockBrandingEnabled(syncService)) {
+ titleId = IDS_IOS_SAVE_PASSWORDS_SMART_LOCK;
+ }
+
+ _savePasswordsDetailItem.reset(
+ [[self detailItemWithType:ItemTypeSavedPasswords
+ text:l10n_util::GetNSString(titleId)
+ detailText:passwordsDetail] retain]);
+
+ return _savePasswordsDetailItem;
+}
+
+- (CollectionViewItem*)autoFillDetailItem {
+ BOOL autofillEnabled = _mainBrowserState->GetPrefs()->GetBoolean(
+ autofill::prefs::kAutofillEnabled);
+ NSString* autofillDetail = autofillEnabled
+ ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
+ : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
+ _autoFillDetailItem.reset(
+ [[self detailItemWithType:ItemTypeAutofill
+ text:l10n_util::GetNSString(IDS_IOS_AUTOFILL)
+ detailText:autofillDetail] retain]);
+
+ return _autoFillDetailItem;
+}
+
+- (CollectionViewItem*)nativeAppsDetailItem {
+ return [self
+ detailItemWithType:ItemTypeNativeApps
+ text:l10n_util::GetNSString(IDS_IOS_GOOGLE_APPS_SM_SETTINGS)
+ detailText:nil];
+}
+
+- (CollectionViewItem*)voiceSearchDetailItem {
+ voice::SpeechInputLocaleConfig* localeConfig =
+ voice::SpeechInputLocaleConfig::GetInstance();
+ voice::SpeechInputLocale locale =
+ _voiceLocaleCode.GetValue().length()
+ ? localeConfig->GetLocaleForCode(_voiceLocaleCode.GetValue())
+ : localeConfig->GetDefaultLocale();
+ NSString* languageName = base::SysUTF16ToNSString(locale.display_name);
+ _voiceSearchDetailItem.reset(
+ [[self detailItemWithType:ItemTypeVoiceSearch
+ text:l10n_util::GetNSString(
+ IDS_IOS_VOICE_SEARCH_SETTING_TITLE)
+ detailText:languageName] retain]);
+ _voiceSearchDetailItem.get().accessibilityIdentifier =
+ kSettingsVoiceSearchCellId;
+ return _voiceSearchDetailItem;
+}
+
+- (CollectionViewItem*)privacyDetailItem {
+ return
+ [self detailItemWithType:ItemTypePrivacy
+ text:l10n_util::GetNSString(
+ IDS_OPTIONS_ADVANCED_SECTION_TITLE_PRIVACY)
+ detailText:nil];
+}
+
+- (CollectionViewItem*)contentSettingsDetailItem {
+ return [self
+ detailItemWithType:ItemTypeContentSettings
+ text:l10n_util::GetNSString(IDS_IOS_CONTENT_SETTINGS_TITLE)
+ detailText:nil];
+}
+
+- (CollectionViewItem*)bandwidthManagementDetailItem {
+ return [self detailItemWithType:ItemTypeBandwidth
+ text:l10n_util::GetNSString(
+ IDS_IOS_BANDWIDTH_MANAGEMENT_SETTINGS)
+ detailText:nil];
+}
+
+- (CollectionViewItem*)aboutChromeDetailItem {
+ return [self detailItemWithType:ItemTypeAboutChrome
+ text:l10n_util::GetNSString(IDS_IOS_PRODUCT_NAME)
+ detailText:nil];
+}
+
+- (CollectionViewSwitchItem*)showMemoryDebugSwitchItem {
+ CollectionViewSwitchItem* showMemoryDebugSwitchItem =
+ [self switchItemWithType:ItemTypeMemoryDebugging
+ title:@"Show memory debug tools"
+ withDefaultsKey:nil];
+ showMemoryDebugSwitchItem.on = [_showMemoryDebugToolsEnabled value];
+
+ return showMemoryDebugSwitchItem;
+}
+#if CHROMIUM_BUILD && !defined(NDEBUG)
+
+- (CollectionViewSwitchItem*)viewSourceSwitchItem {
+ return [self switchItemWithType:ItemTypeViewSource
+ title:@"View source menu"
+ withDefaultsKey:kDevViewSourceKey];
+}
+
+- (CollectionViewSwitchItem*)logJavascriptConsoleSwitchItem {
+ return [self switchItemWithType:ItemTypeLogJavascript
+ title:@"Log JS"
+ withDefaultsKey:kLogJavascriptKey];
+}
+
+- (CollectionViewSwitchItem*)showAutofillTypePredictionsSwitchItem {
+ return [self switchItemWithType:ItemTypeShowAutofillTypePredictions
+ title:@"Show Autofill type predictions"
+ withDefaultsKey:kShowAutofillTypePredictionsKey];
+}
+
+- (CollectionViewDetailItem*)materialCatalogDetailItem {
+ return [self detailItemWithType:ItemTypeCellCatalog
+ text:@"Cell Catalog"
+ detailText:nil];
+}
+#endif // CHROMIUM_BUILD && !defined(NDEBUG)
+
+#pragma mark Item Updaters
+
+- (void)updateSearchCell {
+ NSString* defaultSearchEngineName =
+ base::SysUTF16ToNSString(GetDefaultSearchEngineName(
+ ios::TemplateURLServiceFactory::GetForBrowserState(
+ _mainBrowserState)));
+
+ _defaultSearchEngineItem.get().detailText = defaultSearchEngineName;
+ [self reconfigureCellsForItems:@[ _defaultSearchEngineItem ]
+ inSectionWithIdentifier:SectionIdentifierBasics];
+}
+
+#pragma mark Item Constructors
+
+- (CollectionViewDetailItem*)detailItemWithType:(NSInteger)type
+ text:(NSString*)text
+ detailText:(NSString*)detailText {
+ CollectionViewDetailItem* detailItem =
+ [[[CollectionViewDetailItem alloc] initWithType:type] autorelease];
+ detailItem.text = text;
+ detailItem.detailText = detailText;
+ detailItem.accessoryType = MDCCollectionViewCellAccessoryDisclosureIndicator;
+ detailItem.accessibilityTraits |= UIAccessibilityTraitButton;
+
+ return detailItem;
+}
+
+- (CollectionViewSwitchItem*)switchItemWithType:(NSInteger)type
+ title:(NSString*)title
+ withDefaultsKey:(NSString*)key {
+ CollectionViewSwitchItem* switchItem =
+ [[[CollectionViewSwitchItem alloc] initWithType:type] autorelease];
+ switchItem.text = title;
+ if (key) {
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+ switchItem.on = [defaults boolForKey:key];
+ }
+
+ return switchItem;
+}
+
+#pragma mark - UICollectionViewDataSource
+
+- (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
+ cellForItemAtIndexPath:(NSIndexPath*)indexPath {
+ UICollectionViewCell* cell =
+ [super collectionView:collectionView cellForItemAtIndexPath:indexPath];
+ NSInteger itemType =
+ [self.collectionViewModel itemTypeForIndexPath:indexPath];
+
+ if ([cell isKindOfClass:[CollectionViewDetailCell class]]) {
+ CollectionViewDetailCell* detailCell =
+ base::mac::ObjCCastStrict<CollectionViewDetailCell>(cell);
+ if (itemType == ItemTypeSavedPasswords) {
+ scoped_refptr<password_manager::PasswordStore> passwordStore =
+ IOSChromePasswordStoreFactory::GetForBrowserState(
+ _mainBrowserState, ServiceAccessType::EXPLICIT_ACCESS);
+ if (!passwordStore) {
+ // The password store factory returns a NULL password store if something
+ // goes wrong during the password store initialization. Disable the save
+ // passwords cell in this case.
+ LOG(ERROR) << "Save passwords cell was disabled as the password store"
+ " cannot be created.";
+ [detailCell setUserInteractionEnabled:NO];
+ detailCell.textLabel.textColor = [[MDCPalette greyPalette] tint500];
+ detailCell.detailTextLabel.textColor =
+ [[MDCPalette greyPalette] tint400];
+ }
+ } else {
+ [detailCell setUserInteractionEnabled:YES];
+ detailCell.textLabel.textColor = [[MDCPalette greyPalette] tint900];
+ detailCell.detailTextLabel.textColor = [[MDCPalette greyPalette] tint500];
+ }
+ }
+
+ switch (itemType) {
+ case ItemTypeMemoryDebugging: {
+ CollectionViewSwitchCell* switchCell =
+ base::mac::ObjCCastStrict<CollectionViewSwitchCell>(cell);
+ [switchCell.switchView addTarget:self
+ action:@selector(memorySwitchToggled:)
+ forControlEvents:UIControlEventValueChanged];
+ break;
+ }
+ case ItemTypeViewSource: {
+ CollectionViewSwitchCell* switchCell =
+ base::mac::ObjCCastStrict<CollectionViewSwitchCell>(cell);
+ [switchCell.switchView addTarget:self
+ action:@selector(viewSourceSwitchToggled:)
+ forControlEvents:UIControlEventValueChanged];
+ break;
+ }
+ case ItemTypeLogJavascript: {
+ CollectionViewSwitchCell* switchCell =
+ base::mac::ObjCCastStrict<CollectionViewSwitchCell>(cell);
+ [switchCell.switchView addTarget:self
+ action:@selector(logJSSwitchToggled:)
+ forControlEvents:UIControlEventValueChanged];
+ break;
+ }
+ case ItemTypeShowAutofillTypePredictions: {
+ CollectionViewSwitchCell* switchCell =
+ base::mac::ObjCCastStrict<CollectionViewSwitchCell>(cell);
+ [switchCell.switchView addTarget:self
+ action:@selector(showAutoFillSwitchToggled:)
+ forControlEvents:UIControlEventValueChanged];
+ break;
+ }
+ default:
+ break;
+ }
+
+ return cell;
+}
+
+- (UICollectionReusableView*)collectionView:(UICollectionView*)collectionView
+ viewForSupplementaryElementOfKind:(NSString*)kind
+ atIndexPath:(NSIndexPath*)indexPath {
+ UICollectionReusableView* view = [super collectionView:collectionView
+ viewForSupplementaryElementOfKind:kind
+ atIndexPath:indexPath];
+
+ MDCCollectionViewTextCell* textCell =
+ base::mac::ObjCCast<MDCCollectionViewTextCell>(view);
+ if (textCell) {
+ textCell.textLabel.textColor = [[MDCPalette greyPalette] tint500];
+ }
+ return view;
+}
+
+#pragma mark UICollectionViewDelegate
+
+- (void)collectionView:(UICollectionView*)collectionView
+ didSelectItemAtIndexPath:(NSIndexPath*)indexPath {
+ [super collectionView:collectionView didSelectItemAtIndexPath:indexPath];
+
+ id object = [self.collectionViewModel itemAtIndexPath:indexPath];
+ if ([object respondsToSelector:@selector(isEnabled)] &&
+ ![object performSelector:@selector(isEnabled)]) {
+ // Don't perform any action if the cell isn't enabled.
+ return;
+ }
+
+ NSInteger itemType =
+ [self.collectionViewModel itemTypeForIndexPath:indexPath];
+
+ base::scoped_nsobject<UIViewController> controller;
+
+ switch (itemType) {
+ case ItemTypeSignInButton:
+ [self showSignIn];
+ break;
+ case ItemTypeAccount:
+ controller.reset([[AccountsCollectionViewController alloc]
+ initWithBrowserState:_mainBrowserState
+ closeSettingsOnAddAccount:NO]);
+ break;
+ case ItemTypeSearchEngine:
+ controller.reset([[SearchEngineSettingsCollectionViewController alloc]
+ initWithBrowserState:_mainBrowserState]);
+ break;
+ case ItemTypeSavedPasswords: {
+ controller.reset([[SavePasswordsCollectionViewController alloc]
+ initWithBrowserState:_mainBrowserState]);
+ break;
+ }
+ case ItemTypeAutofill:
+ controller.reset([[AutofillCollectionViewController alloc]
+ initWithBrowserState:_mainBrowserState]);
+ break;
+ case ItemTypeNativeApps:
+ controller.reset([[NativeAppsCollectionViewController alloc]
+ initWithURLRequestContextGetter:_currentBrowserState
+ ->GetRequestContext()]);
+ break;
+ case ItemTypeVoiceSearch:
+ controller.reset([[VoicesearchCollectionViewController alloc]
+ initWithPrefs:_mainBrowserState->GetPrefs()]);
+ break;
+ case ItemTypePrivacy:
+ controller.reset([[PrivacyCollectionViewController alloc]
+ initWithBrowserState:_mainBrowserState]);
+ break;
+ case ItemTypeContentSettings:
+ controller.reset([[ContentSettingsCollectionViewController alloc]
+ initWithBrowserState:_mainBrowserState]);
+ break;
+ case ItemTypeBandwidth:
+ controller.reset([[BandwidthManagementCollectionViewController alloc]
+ initWithBrowserState:_mainBrowserState]);
+ break;
+ case ItemTypeAboutChrome:
+ controller.reset([[AboutChromeCollectionViewController alloc] init]);
+ break;
+ case ItemTypeMemoryDebugging:
+ case ItemTypeViewSource:
+ case ItemTypeLogJavascript:
+ case ItemTypeShowAutofillTypePredictions:
+ // Taps on these don't do anything. They have a switch as accessory view
+ // and only the switch is tappable.
+ break;
+ case ItemTypeCellCatalog:
+ controller.reset([[MaterialCellCatalogViewController alloc] init]);
+ break;
+ default:
+ break;
+ }
+
+ if (controller) {
+ [self.navigationController pushViewController:controller animated:YES];
+ }
+}
+
+#pragma mark MDCCollectionViewStylingDelegate
+
+- (CGFloat)collectionView:(UICollectionView*)collectionView
+ cellHeightAtIndexPath:(NSIndexPath*)indexPath {
+ CollectionViewItem* item =
+ [self.collectionViewModel itemAtIndexPath:indexPath];
+
+ if (item.type == ItemTypeAccount) {
+ return MDCCellDefaultTwoLineHeight;
+ }
+
+ if (item.type == ItemTypeSignInButton) {
+ return MDCCellDefaultThreeLineHeight;
+ }
+
+ return MDCCellDefaultOneLineHeight;
+}
+
+- (BOOL)collectionView:(UICollectionView*)collectionView
+ hidesInkViewAtIndexPath:(NSIndexPath*)indexPath {
+ NSInteger type = [self.collectionViewModel itemTypeForIndexPath:indexPath];
+ switch (type) {
+ case ItemTypeMemoryDebugging:
+ case ItemTypeViewSource:
+ case ItemTypeLogJavascript:
+ case ItemTypeShowAutofillTypePredictions:
+ return YES;
+ default:
+ return NO;
+ }
+}
+
+#pragma mark Switch Actions
+
+- (void)memorySwitchToggled:(UISwitch*)sender {
+ NSIndexPath* switchPath =
+ [self.collectionViewModel indexPathForItemType:ItemTypeMemoryDebugging
+ sectionIdentifier:SectionIdentifierDebug];
+
+ CollectionViewSwitchItem* switchItem =
+ base::mac::ObjCCastStrict<CollectionViewSwitchItem>(
+ [self.collectionViewModel itemAtIndexPath:switchPath]);
+
+ BOOL newSwitchValue = sender.isOn;
+ switchItem.on = newSwitchValue;
+ [_showMemoryDebugToolsEnabled setValue:newSwitchValue];
+}
+
+#if CHROMIUM_BUILD && !defined(NDEBUG)
+- (void)viewSourceSwitchToggled:(UISwitch*)sender {
+ NSIndexPath* switchPath =
+ [self.collectionViewModel indexPathForItemType:ItemTypeViewSource
+ sectionIdentifier:SectionIdentifierDebug];
+
+ CollectionViewSwitchItem* switchItem =
+ base::mac::ObjCCastStrict<CollectionViewSwitchItem>(
+ [self.collectionViewModel itemAtIndexPath:switchPath]);
+
+ BOOL newSwitchValue = sender.isOn;
+ switchItem.on = newSwitchValue;
+ [self setBooleanNSUserDefaultsValue:newSwitchValue forKey:kDevViewSourceKey];
+}
+
+- (void)logJSSwitchToggled:(UISwitch*)sender {
+ NSIndexPath* switchPath =
+ [self.collectionViewModel indexPathForItemType:ItemTypeLogJavascript
+ sectionIdentifier:SectionIdentifierDebug];
+
+ CollectionViewSwitchItem* switchItem =
+ base::mac::ObjCCastStrict<CollectionViewSwitchItem>(
+ [self.collectionViewModel itemAtIndexPath:switchPath]);
+
+ BOOL newSwitchValue = sender.isOn;
+ switchItem.on = newSwitchValue;
+ [self setBooleanNSUserDefaultsValue:newSwitchValue forKey:kLogJavascriptKey];
+}
+
+- (void)showAutoFillSwitchToggled:(UISwitch*)sender {
+ NSIndexPath* switchPath = [self.collectionViewModel
+ indexPathForItemType:ItemTypeShowAutofillTypePredictions
+ sectionIdentifier:SectionIdentifierDebug];
+
+ CollectionViewSwitchItem* switchItem =
+ base::mac::ObjCCastStrict<CollectionViewSwitchItem>(
+ [self.collectionViewModel itemAtIndexPath:switchPath]);
+
+ BOOL newSwitchValue = sender.isOn;
+ switchItem.on = newSwitchValue;
+ [self setBooleanNSUserDefaultsValue:newSwitchValue
+ forKey:kShowAutofillTypePredictionsKey];
+}
+#endif // CHROMIUM_BUILD && !defined(NDEBUG)
+
+#pragma mark Private methods
+
+// Sets the NSUserDefaults BOOL |value| for |key|.
+- (void)setBooleanNSUserDefaultsValue:(BOOL)value forKey:(NSString*)key {
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+ [defaults setBool:value forKey:key];
+ [defaults synchronize];
+}
+
+// Returns YES if a "Debug" section should be shown. This is always true for
+// Chromium builds, but for official builds it is gated by an experimental flag
+// because the "Debug" section should never be showing in stable channel.
+- (BOOL)hasDebugSection {
+#if CHROMIUM_BUILD && !defined(NDEBUG)
+ return YES;
+#else
+ if (experimental_flags::IsMemoryDebuggingEnabled()) {
+ return YES;
+ }
+ return NO;
+#endif // CHROMIUM_BUILD && !defined(NDEBUG)
+}
+
+// Updates the identity cell.
+- (void)updateIdentityAccountItem:
+ (CollectionViewAccountItem*)identityAccountItem {
+ AuthenticationService* authService =
+ AuthenticationServiceFactory::GetForBrowserState(_mainBrowserState);
+ _identity.reset([authService->GetAuthenticatedIdentity() retain]);
+ if (!_identity) {
+ // This could occur during the sign out process. Just ignore as the account
+ // cell will be replaced by the "Sign in" button.
+ return;
+ }
+ identityAccountItem.image = [self userAccountImage];
+
+ SyncSetupService* syncSetupService =
+ SyncSetupServiceFactory::GetForBrowserState(_mainBrowserState);
+
+ if (!syncSetupService->HasFinishedInitialSetup()) {
+ identityAccountItem.detailText =
+ l10n_util::GetNSString(IDS_IOS_SYNC_SETUP_IN_PROGRESS);
+ identityAccountItem.shouldDisplayError = NO;
+ return;
+ }
+ identityAccountItem.shouldDisplayError =
+ !ios_internal::sync::IsTransientSyncError(
+ syncSetupService->GetSyncServiceState());
+ identityAccountItem.text = [_identity userFullName];
+ if (identityAccountItem.shouldDisplayError) {
+ identityAccountItem.detailText =
+ ios_internal::sync::GetSyncErrorDescriptionForBrowserState(
+ _mainBrowserState);
+ } else {
+ identityAccountItem.detailText =
+ syncSetupService->IsSyncEnabled()
+ ? l10n_util::GetNSStringF(
+ IDS_IOS_SIGN_IN_TO_CHROME_SETTING_SYNCING,
+ base::SysNSStringToUTF16([_identity userEmail]))
+ : l10n_util::GetNSString(
+ IDS_IOS_SIGN_IN_TO_CHROME_SETTING_SYNC_OFF);
+ }
+}
+
+- (void)reloadAccountCell {
+ if (![self.collectionViewModel hasItemForItemType:ItemTypeAccount
+ sectionIdentifier:SectionIdentifierSignIn]) {
+ return;
+ }
+ NSIndexPath* accountCellIndexPath =
+ [self.collectionViewModel indexPathForItemType:ItemTypeAccount
+ sectionIdentifier:SectionIdentifierSignIn];
+ CollectionViewAccountItem* identityAccountItem =
+ base::mac::ObjCCast<CollectionViewAccountItem>(
+ [self.collectionViewModel itemAtIndexPath:accountCellIndexPath]);
+ if (identityAccountItem) {
+ [self updateIdentityAccountItem:identityAccountItem];
+ [self reconfigureCellsForItems:@[ identityAccountItem ]
+ inSectionWithIdentifier:SectionIdentifierSignIn];
+ }
+}
+
+#pragma mark Sign in
+
+- (void)showSignIn {
+ base::RecordAction(base::UserMetricsAction("Signin_Signin_FromSettings"));
+ DCHECK(!_signinInteractionController);
+ _signinInteractionController.reset([[SigninInteractionController alloc]
+ initWithBrowserState:_mainBrowserState
+ presentingViewController:self.navigationController
+ isPresentedOnSettings:YES
+ signInAccessPoint:signin_metrics::AccessPoint::
+ ACCESS_POINT_SETTINGS]);
+
+ base::WeakNSObject<SettingsCollectionViewController> weakSelf(self);
+ [_signinInteractionController signInWithCompletion:^(BOOL success) {
+ [weakSelf didFinishSignin:success];
+ }
+ viewController:self];
+}
+
+- (void)didFinishSignin:(BOOL)signedIn {
+ _signinInteractionController.reset();
+}
+
+#pragma mark NotificationBridgeDelegate
+
+- (void)onSignInStateChanged {
+ // Sign in state changes are rare. Just reload the entire collection when this
+ // happens.
+ [self reloadData];
+}
+
+#pragma mark SettingsControllerProtocol
+
+- (void)settingsWillBeDismissed {
+ [_signinInteractionController cancel];
+ [self stopBrowserStateServiceObservers];
+}
+
+#pragma mark SyncObserverModelBridge
+
+- (void)onSyncStateChanged {
+ [self reloadAccountCell];
+}
+
+#pragma mark - IdentityRefreshLogic
+
+// Image used for loggedin user account that supports caching.
+- (UIImage*)userAccountImage {
+ UIImage* image = ios::GetChromeBrowserProvider()
+ ->GetChromeIdentityService()
+ ->GetCachedAvatarForIdentity(_identity);
+ if (!image) {
+ image = ios::GetChromeBrowserProvider()
+ ->GetSigninResourcesProvider()
+ ->GetDefaultAvatar();
+ // No cached image, trigger a fetch, which will notify all observers
+ // (including the corresponding AccountViewBase).
+ ios::GetChromeBrowserProvider()
+ ->GetChromeIdentityService()
+ ->GetAvatarForIdentity(_identity, ^(UIImage*){
+ });
+ }
+
+ // If the currently used image has already been resized, use it.
+ if (_resizedImage && _oldImage.get() == image)
+ return _resizedImage;
+
+ _oldImage.reset(image);
+
+ // Resize the profile image.
+ CGFloat dimension = kAccountProfilePhotoDimension;
+ if (image.size.width != dimension || image.size.height != dimension) {
+ image = ResizeImage(image, CGSizeMake(dimension, dimension),
+ ProjectionMode::kAspectFit);
+ }
+ _resizedImage.reset([image retain]);
+ return _resizedImage;
+}
+
+#pragma mark ChromeIdentityServiceObserver
+
+- (void)onProfileUpdate:(ChromeIdentity*)identity {
+ if (identity == _identity) {
+ [self reloadAccountCell];
+ }
+}
+
+- (void)onChromeIdentityServiceWillBeDestroyed {
+ _identityServiceObserver.reset();
+}
+
+#pragma mark - BooleanObserver
+
+- (void)booleanDidChange:(id<ObservableBoolean>)observableBoolean {
+ DCHECK_EQ(observableBoolean, _showMemoryDebugToolsEnabled.get());
+ // Update the Item.
+ _showMemoryDebugToolsItem.get().on = [_showMemoryDebugToolsEnabled value];
+
+ // Update the Cell.
+ [self reconfigureCellsForItems:@[ _showMemoryDebugToolsItem ]
+ inSectionWithIdentifier:SectionIdentifierDebug];
+}
+
+#pragma mark - PrefObserverDelegate
+
+- (void)onPreferenceChanged:(const std::string&)preferenceName {
+ if (preferenceName == prefs::kVoiceSearchLocale) {
+ voice::SpeechInputLocaleConfig* localeConfig =
+ voice::SpeechInputLocaleConfig::GetInstance();
+ voice::SpeechInputLocale locale =
+ _voiceLocaleCode.GetValue().length()
+ ? localeConfig->GetLocaleForCode(_voiceLocaleCode.GetValue())
+ : localeConfig->GetDefaultLocale();
+ NSString* languageName = base::SysUTF16ToNSString(locale.display_name);
+ _voiceSearchDetailItem.get().detailText = languageName;
+ [self reconfigureCellsForItems:@[ _voiceSearchDetailItem ]
+ inSectionWithIdentifier:SectionIdentifierAdvanced];
+ }
+
+ if (preferenceName ==
+ password_manager::prefs::kPasswordManagerSavingEnabled) {
+ BOOL savePasswordsEnabled =
+ _mainBrowserState->GetPrefs()->GetBoolean(preferenceName);
+ NSString* passwordsDetail =
+ savePasswordsEnabled ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
+ : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
+
+ _savePasswordsDetailItem.get().detailText = passwordsDetail;
+ [self reconfigureCellsForItems:@[ _savePasswordsDetailItem ]
+ inSectionWithIdentifier:SectionIdentifierBasics];
+ }
+
+ if (preferenceName == autofill::prefs::kAutofillEnabled) {
+ BOOL autofillEnabled =
+ _mainBrowserState->GetPrefs()->GetBoolean(preferenceName);
+ NSString* autofillDetail =
+ autofillEnabled ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
+ : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
+ _autoFillDetailItem.get().detailText = autofillDetail;
+ [self reconfigureCellsForItems:@[ _autoFillDetailItem ]
+ inSectionWithIdentifier:SectionIdentifierBasics];
+ }
+}
+
+@end

Powered by Google App Engine
This is Rietveld 408576698