chromium/ios/chrome/browser/autofill/ui_bundled/autofill_country_selection_table_view_controller.mm

// Copyright 2023 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/autofill/ui_bundled/autofill_country_selection_table_view_controller.h"

#import "base/apple/foundation_util.h"
#import "base/strings/sys_string_conversions.h"
#import "components/autofill/ios/common/features.h"
#import "ios/chrome/browser/autofill/ui_bundled/autofill_constants.h"
#import "ios/chrome/browser/autofill/ui_bundled/cells/country_item.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_navigation_controller_constants.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/table_view/table_view_cells_constants.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util_mac.h"

namespace {

typedef NS_ENUM(NSInteger, SectionIdentifier) {
  SectionIdentifierCountries = kSectionIdentifierEnumZero,
};

}  // namespace

@interface AutofillCountrySelectionTableViewController () <
    UISearchControllerDelegate,
    UISearchResultsUpdating> {
  // The delegate passed to this instance.
  __weak id<AutofillCountrySelectionTableViewControllerDelegate> _delegate;

  // This ViewController's search controller.
  UISearchController* _searchController;

  // Denotes the currently selected country.
  NSString* _currentlySelectedCountry;

  // The current search filter. May be nil.
  NSPredicate* _searchPredicate;

  // Scrim overlay covering the entire tableView when the search bar is focused.
  UIControl* _scrimView;

  // The fetched country list.
  NSArray<CountryItem*>* _allCountries;

  // If YES, denotes that the view is shown in the settings.
  BOOL _settingsView;
}

@end

@implementation AutofillCountrySelectionTableViewController

- (instancetype)initWithDelegate:
                    (id<AutofillCountrySelectionTableViewControllerDelegate>)
                        delegate
                 selectedCountry:(NSString*)selectedCountry
                    allCountries:(NSArray<CountryItem*>*)allCountries
                    settingsView:(BOOL)settingsView {
  DCHECK(delegate);

  UITableViewStyle viewStyle =
      (settingsView || base::FeatureList::IsEnabled(
                           kAutofillDynamicallyLoadsFieldsForAddressInput))
          ? ChromeTableViewStyle()
          : UITableViewStylePlain;

  self = [super initWithStyle:viewStyle];
  if (self) {
    _delegate = delegate;
    _currentlySelectedCountry = selectedCountry;
    _allCountries = allCountries;
    _settingsView = settingsView;
  }
  return self;
}

#pragma mark - UIViewController

- (void)viewDidLoad {
  [super viewDidLoad];

  self.title =
      l10n_util::GetNSString(_settingsView ? IDS_IOS_AUTOFILL_EDIT_ADDRESS
                                           : IDS_IOS_AUTOFILL_SELECT_COUNTRY);

  if (!_settingsView && !base::FeatureList::IsEnabled(
                            kAutofillDynamicallyLoadsFieldsForAddressInput)) {
    self.view.backgroundColor = [UIColor colorNamed:kBackgroundColor];
    self.tableView.sectionHeaderHeight = 0;
    self.tableView.sectionFooterHeight = 0;
  }

  [self.tableView
      setSeparatorInset:UIEdgeInsetsMake(0, kTableViewHorizontalSpacing, 0, 0)];

  // Search controller.
  _searchController =
      [[UISearchController alloc] initWithSearchResultsController:nil];
  _searchController.obscuresBackgroundDuringPresentation = NO;
  _searchController.searchResultsUpdater = self;
  _searchController.searchBar.accessibilityIdentifier =
      kAutofillCountrySelectionTableViewId;
  // Presentation of searchController will walk up the view controller hierarchy
  // until it finds the root view controller or one that defines a presentation
  // context. Make this view controller the presentation context so that the
  // searchController does not present on top of the navigation controller.
  self.definesPresentationContext = YES;
  // Place the search bar in the navigation bar.
  self.navigationItem.searchController = _searchController;
  self.navigationItem.hidesSearchBarWhenScrolling = NO;
  self.navigationItem.largeTitleDisplayMode =
      UINavigationItemLargeTitleDisplayModeAutomatic;

  // Scrim.
  _scrimView = [[UIControl alloc] init];
  _scrimView.alpha = 0.0f;
  _scrimView.backgroundColor = UIColor.clearColor;
  _scrimView.translatesAutoresizingMaskIntoConstraints = NO;
  _scrimView.accessibilityIdentifier = kAutofillCountrySelectionSearchScrimId;
  [_scrimView addTarget:self
                 action:@selector(dismissSearchController:)
       forControlEvents:UIControlEventTouchUpInside];

  [self loadModel];
}

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

  // Autoscroll to the selected country.
  for (CountryItem* item in [self.tableViewModel
           itemsInSectionWithIdentifier:SectionIdentifierCountries]) {
    if (item.accessoryType == UITableViewCellAccessoryCheckmark) {
      NSIndexPath* indexPath = [self.tableViewModel indexPathForItem:item];
      [self.tableView scrollToRowAtIndexPath:indexPath
                            atScrollPosition:UITableViewScrollPositionMiddle
                                    animated:NO];
    }
  }
}

#pragma mark - LegacyChromeTableViewController

- (void)loadModel {
  [super loadModel];

  [self.tableViewModel addSectionWithIdentifier:SectionIdentifierCountries];
  [self populateCountriesSection];
}

#pragma mark - UITableViewDelegate

- (void)tableView:(UITableView*)tableView
    didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
  if (@available(iOS 16.0, *)) {
    return;
  }

  CountryItem* item = base::apple::ObjCCastStrict<CountryItem>(
      [self.tableViewModel itemAtIndexPath:indexPath]);
  [_delegate didSelectCountry:item];
}

- (void)tableView:(UITableView*)tableView
    performPrimaryActionForRowAtIndexPath:(NSIndexPath*)indexPath {
  CountryItem* item = base::apple::ObjCCastStrict<CountryItem>(
      [self.tableViewModel itemAtIndexPath:indexPath]);
  [_delegate didSelectCountry:item];
}

#pragma mark - UISearchResultsUpdating

- (void)updateSearchResultsForSearchController:
    (UISearchController*)searchController {
  NSString* searchText = searchController.searchBar.text;

  // Set the current search filter to filter the languages based on the display
  // name of the language in the current locale and the language locale. The
  // search is case insensitive and diacritic insensitive. If the search text is
  // empty all languages will be displayed.
  _searchPredicate = [[NSPredicate
      predicateWithFormat:@"$searchText.length == 0 OR text CONTAINS[cd] "
                          @"$searchText"]
      predicateWithSubstitutionVariables:@{@"searchText" : searchText}];

  // Show the scrim overlay only if the search text is empty and the search
  // controller is active (it is not being dismissed); Otherwise hide it.
  if (searchText.length == 0 && _searchController.active) {
    [self showScrim];
  } else {
    [self hideScrim];
  }

  [self updateCountriesSection];
}

#pragma mark - Helper methods

// Populates the country items in the section.
- (void)populateCountriesSection {
  TableViewModel* model = self.tableViewModel;

  // Filter the countries items based on the current search text, if applicable.
  NSArray<CountryItem*>* filteredSupportedCountries = _allCountries;
  if (_searchPredicate) {
    filteredSupportedCountries = [filteredSupportedCountries
        filteredArrayUsingPredicate:_searchPredicate];
  }

  for (CountryItem* item in filteredSupportedCountries) {
    [model addItem:item toSectionWithIdentifier:SectionIdentifierCountries];
  }
}

// Shows scrim overlay and hide toolbar.
- (void)showScrim {
  if (_scrimView.alpha < 1.0f) {
    _scrimView.alpha = 0.0f;
    [self.tableView addSubview:_scrimView];
    // We attach our constraints to the superview because the tableView is
    // a scrollView and it seems that we get an empty frame when attaching to
    // it.
    AddSameConstraints(_scrimView, self.view.superview);
    self.tableView.accessibilityElementsHidden = YES;
    self.tableView.scrollEnabled = NO;
    __weak __typeof(self) weakSelf = self;
    [UIView animateWithDuration:kTableViewNavigationScrimFadeDuration
                     animations:^{
                       AutofillCountrySelectionTableViewController* strongSelf =
                           weakSelf;
                       strongSelf->_scrimView.alpha = 1.0f;
                       [strongSelf.view layoutIfNeeded];
                     }];
  }
}

// Hides scrim and restore toolbar.
- (void)hideScrim {
  if (_scrimView.alpha > 0.0f) {
    __weak __typeof(self) weakSelf = self;
    [UIView animateWithDuration:kTableViewNavigationScrimFadeDuration
        animations:^{
          AutofillCountrySelectionTableViewController* strongSelf = weakSelf;
          strongSelf->_scrimView.alpha = 0.0f;
        }
        completion:^(BOOL finished) {
          AutofillCountrySelectionTableViewController* strongSelf = weakSelf;
          [strongSelf->_scrimView removeFromSuperview];
          strongSelf.tableView.accessibilityElementsHidden = NO;
          strongSelf.tableView.scrollEnabled = YES;
        }];
  }
}

// Dismisses the search controller when the scrim overlay is tapped.
- (void)dismissSearchController:(UIControl*)sender {
  _searchController.active = NO;
}

// Reloads the countries items in thes ection.
- (void)updateCountriesSection {
  // Update the model.
  [self.tableViewModel
      deleteAllItemsFromSectionWithIdentifier:SectionIdentifierCountries];
  [self populateCountriesSection];

  // Update the table view.
  NSUInteger index = [self.tableViewModel
      sectionForSectionIdentifier:SectionIdentifierCountries];
  [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:index]
                withRowAnimation:UITableViewRowAnimationNone];
}

#pragma mark - SettingsControllerProtocol

- (void)reportDismissalUserAction {
  // TODO(crbug.com/40253248): Record for this VC.
}

- (void)reportBackUserAction {
  // TODO(crbug.com/40253248): Record for this VC.
}

@end