chromium/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_table_view_controller.mm

// Copyright 2019 The Chromium Authors
// 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/google_services/manage_sync_settings_table_view_controller.h"

#import "base/apple/foundation_util.h"
#import "ios/chrome/browser/net/model/crurl.h"
#import "ios/chrome/browser/policy/model/management_state.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_info_button_cell.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_switch_cell.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
#import "ios/chrome/browser/ui/authentication/cells/central_account_view.h"
#import "ios/chrome/browser/ui/settings/cells/settings_image_detail_text_cell.h"
#import "ios/chrome/browser/ui/settings/cells/sync_switch_item.h"
#import "ios/chrome/browser/ui/settings/elements/enterprise_info_popover_view_controller.h"
#import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_constants.h"
#import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_service_delegate.h"
#import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_table_view_controller_model_delegate.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/grit/ios_strings.h"
#import "net/base/apple/url_conversions.h"
#import "ui/base/l10n/l10n_util.h"

namespace {

// Table view customized header heights.
CGFloat kAdvancedSettingsSectionHeaderHeightPointSize = 26.;
CGFloat kSignOutSectionHeaderHeightPointSize = 26.;
CGFloat kDefaultSectionHeaderHeightPointSize = 10.;

// Table view customized footer heights.
CGFloat kDefaultSectionFooterHeightPointSize = 10.;

}  // namespace

@interface ManageSyncSettingsTableViewController () <
    PopoverLabelViewControllerDelegate>
@end

@implementation ManageSyncSettingsTableViewController

#pragma mark - UIViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  self.tableView.accessibilityIdentifier =
      kManageSyncTableViewAccessibilityIdentifier;
}

- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];
  [self reloadData];
}

- (void)didMoveToParentViewController:(UIViewController*)parent {
  [super didMoveToParentViewController:parent];
  if (!parent) {
    [self.presentationDelegate
        manageSyncSettingsTableViewControllerWasRemoved:self];
  }
}

#pragma mark - Private

- (void)switchAction:(UISwitch*)sender {
  TableViewModel* model = self.tableViewModel;
  NSIndexPath* indexPath = [model indexPathForItemType:sender.tag];
  DCHECK(indexPath);
  SyncSwitchItem* syncSwitchItem = base::apple::ObjCCastStrict<SyncSwitchItem>(
      [model itemAtIndexPath:indexPath]);
  DCHECK(syncSwitchItem);
  [self.serviceDelegate toggleSwitchItem:syncSwitchItem withValue:sender.isOn];
}

#pragma mark - UITableViewDataSource

- (UITableViewCell*)tableView:(UITableView*)tableView
        cellForRowAtIndexPath:(NSIndexPath*)indexPath {
  UITableViewCell* cell = [super tableView:tableView
                     cellForRowAtIndexPath:indexPath];
  if ([cell isKindOfClass:[TableViewSwitchCell class]]) {
    TableViewSwitchCell* switchCell =
        base::apple::ObjCCastStrict<TableViewSwitchCell>(cell);
    [switchCell.switchView addTarget:self
                              action:@selector(switchAction:)
                    forControlEvents:UIControlEventValueChanged];
    ListItem* item = [self.tableViewModel itemAtIndexPath:indexPath];
    switchCell.switchView.tag = item.type;
  } else if ([cell isKindOfClass:[TableViewInfoButtonCell class]]) {
    TableViewInfoButtonCell* managedCell =
        base::apple::ObjCCastStrict<TableViewInfoButtonCell>(cell);
    managedCell.textLabel.textColor = [UIColor colorNamed:kTextSecondaryColor];
    [managedCell.trailingButton addTarget:self
                                   action:@selector(didTapManagedUIInfoButton:)
                         forControlEvents:UIControlEventTouchUpInside];
  } else if ([cell isKindOfClass:[SettingsImageDetailTextCell class]]) {
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
  }
  return cell;
}

- (UIView*)tableView:(UITableView*)tableView
    viewForFooterInSection:(NSInteger)section {
  UIView* view = [super tableView:tableView viewForFooterInSection:section];

  if (![self.tableViewModel footerForSectionIndex:section]) {
    // Don't set up the footer view when there isn't a footer in the model.
    return view;
  }

  NSInteger sectionIdentifier =
      [self.tableViewModel sectionIdentifierForSectionIndex:section];

  if (sectionIdentifier == ManageAndSignOutSectionIdentifier) {
    TableViewLinkHeaderFooterView* linkView =
        base::apple::ObjCCastStrict<TableViewLinkHeaderFooterView>(view);
    linkView.delegate = self;
  }

  return view;
}

#pragma mark - LegacyChromeTableViewController

- (void)loadModel {
  [super loadModel];
  [self.modelDelegate manageSyncSettingsTableViewControllerLoadModel:self];
}

#pragma mark - ManageSyncSettingsConsumer

- (void)insertSections:(NSIndexSet*)sections rowAnimation:(BOOL)rowAnimation {
  if (!self.tableViewModel) {
    // No need to reload since the model has not been loaded yet.
    return;
  }
  if (rowAnimation) {
    [self.tableView insertSections:sections
                  withRowAnimation:UITableViewRowAnimationMiddle];
  } else {
    [UIView performWithoutAnimation:^{
      [self.tableView beginUpdates];
      [self.tableView insertSections:sections
                    withRowAnimation:UITableViewRowAnimationNone];
      [self.tableView endUpdates];
    }];
  }
}

- (void)deleteSections:(NSIndexSet*)sections rowAnimation:(BOOL)rowAnimation {
  if (!self.tableViewModel) {
    // No need to reload since the model has not been loaded yet.
    return;
  }
  if (rowAnimation) {
    [self.tableView deleteSections:sections
                  withRowAnimation:UITableViewRowAnimationMiddle];
  } else {
    // To avoid animation glitches related to crbug.com/1469539.
    [UIView performWithoutAnimation:^{
      [self.tableView beginUpdates];
      [self.tableView deleteSections:sections
                    withRowAnimation:UITableViewRowAnimationNone];
      [self.tableView endUpdates];
    }];
  }
}

- (void)reloadItem:(TableViewItem*)item {
  if (!self.tableViewModel) {
    // No need to reload since the model has not been loaded yet.
    return;
  }
  if (!item) {
    // No need to reload if the item doesn't exist. indexPathForItem below
    // should handle nil just fine, but doesn't hurt to early return explicitly.
    return;
  }
  NSIndexPath* indexPath = [self.tableViewModel indexPathForItem:item];
  if (!indexPath) {
    // No need to reload if the item is not in the model. This would also cause
    // a crash below since NSArrays cannot contain nil.
    // TODO(crbug.com/40073025): Better understand the crash root cause and
    // CHECK instead of no-op.
    return;
  }
  // To avoid animation glitches related to crbug.com/1469539.
  [UIView performWithoutAnimation:^{
    [self.tableView beginUpdates];
    [self.tableView reloadRowsAtIndexPaths:@[ indexPath ]
                          withRowAnimation:UITableViewRowAnimationNone];
    [self.tableView endUpdates];
  }];
}

- (void)reloadSections:(NSIndexSet*)sections {
  if (!self.tableViewModel) {
    // No need to reload since the model has not been loaded yet.
    return;
  }
  [self.tableView reloadSections:sections
                withRowAnimation:UITableViewRowAnimationNone];
}

- (void)updatePrimaryAccountWithAvatarImage:(UIImage*)avatarImage
                                       name:(NSString*)name
                                      email:(NSString*)email
                            managementState:(ManagementState)managementState {
  CentralAccountView* identityAccountItem = [[CentralAccountView alloc]
        initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, 0)
          avatarImage:avatarImage
                 name:name
                email:email
      managementState:std::move(managementState)];
  self.tableView.tableHeaderView = identityAccountItem;
  [self.tableView reloadData];
}

#pragma mark - UITableViewDelegate

- (void)tableView:(UITableView*)tableView
    didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
  [super tableView:tableView didSelectRowAtIndexPath:indexPath];
  TableViewItem* item = [self.tableViewModel itemAtIndexPath:indexPath];
  CGRect cellRect = [tableView rectForRowAtIndexPath:indexPath];
  [self.serviceDelegate didSelectItem:item cellRect:cellRect];
  [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

- (CGFloat)tableView:(UITableView*)tableView
    heightForHeaderInSection:(NSInteger)section {
  if (self.isAccountStateSignedIn) {
    NSInteger sectionIdentifier =
        [self.tableViewModel sectionIdentifierForSectionIndex:section];
    switch (sectionIdentifier) {
      case SyncDataTypeSectionIdentifier:
        return UITableViewAutomaticDimension;
      case AdvancedSettingsSectionIdentifier:
        return kAdvancedSettingsSectionHeaderHeightPointSize;
      case ManageAndSignOutSectionIdentifier:
        if (![self.tableViewModel hasSectionForSectionIdentifier:
                                      AdvancedSettingsSectionIdentifier]) {
          return kSignOutSectionHeaderHeightPointSize;
        }
        break;
      case SyncErrorsSectionIdentifier:
      case BatchUploadSectionIdentifier:
        return kDefaultSectionHeaderHeightPointSize;
    }
  }
  return ChromeTableViewHeightForHeaderInSection(section);
}

- (CGFloat)tableView:(UITableView*)tableView
    heightForFooterInSection:(NSInteger)section {
  if (self.isAccountStateSignedIn) {
    NSInteger sectionIdentifier =
        [self.tableViewModel sectionIdentifierForSectionIndex:section];
    switch (sectionIdentifier) {
      case SyncDataTypeSectionIdentifier:
      case ManageAndSignOutSectionIdentifier:
        return UITableViewAutomaticDimension;
      case AdvancedSettingsSectionIdentifier:
      case SyncErrorsSectionIdentifier:
        break;
    }
  }
  return kDefaultSectionFooterHeightPointSize;
}

#pragma mark - Sync helpers

// Called when the user clicks on the information button of the managed
// setting's UI. Shows a textual bubble with the information of the enterprise.
- (void)didTapManagedUIInfoButton:(UIButton*)buttonView {
  EnterpriseInfoPopoverViewController* bubbleViewController =
      [[EnterpriseInfoPopoverViewController alloc]
          initWithMessage:self.isAccountStateSignedIn
                              ? l10n_util::GetNSString(
                                    IDS_IOS_ENTERPRISE_MANAGED_SAVE_IN_ACCOUNT)
                              : l10n_util::GetNSString(
                                    IDS_IOS_ENTERPRISE_MANAGED_SYNC)
           enterpriseName:nil];
  [self presentViewController:bubbleViewController animated:YES completion:nil];

  // Disable the button when showing the bubble.
  buttonView.enabled = NO;

  // Set the anchor and arrow direction of the bubble.
  bubbleViewController.popoverPresentationController.sourceView = buttonView;
  bubbleViewController.popoverPresentationController.sourceRect =
      buttonView.bounds;
  bubbleViewController.popoverPresentationController.permittedArrowDirections =
      UIPopoverArrowDirectionAny;
  bubbleViewController.delegate = self;
}

#pragma mark - PopoverLabelViewControllerDelegate

- (void)didTapLinkURL:(NSURL*)URL {
  [self view:nil didTapLinkURL:[[CrURL alloc] initWithNSURL:URL]];
}

@end