chromium/ios/chrome/browser/drive_file_picker/ui/drive_file_picker_table_view_controller.mm

// Copyright 2024 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/drive_file_picker/ui/drive_file_picker_table_view_controller.h"

#import "ios/chrome/browser/drive_file_picker/ui/drive_file_picker_constants.h"
#import "ios/chrome/browser/drive_file_picker/ui/drive_file_picker_mutator.h"
#import "ios/chrome/browser/drive_file_picker/ui/drive_file_picker_navigation_controller.h"
#import "ios/chrome/browser/drive_file_picker/ui/drive_item_identifier.h"
#import "ios/chrome/browser/shared/public/commands/drive_file_picker_commands.h"
#import "ios/chrome/browser/shared/ui/list_model/list_model.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_detail_icon_item.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/browser/ui/menu/browser_action_factory.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.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 {

constexpr CGFloat kCellIconCornerRadius = 10;

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

}  // namespace

@implementation DriveFilePickerTableViewController {
  // The status of file dowload.
  DriveFileDownloadStatus _status;

  // The sorting order.
  DriveItemsSortingOrder _sortingOrder;

  // The sorting type.
  DriveItemsSortingType _sortingType;

  // The Sorting actions
  UIAction* _sortByNameAction;
  UIAction* _sortByModificationTimeAction;
  UIAction* _sortByOpeningTimeAction;

  // The sort button.
  UIBarButtonItem* _sortButton;

  // The selected email from the accounts signed in the device.
  NSString* _selectedEmail;

  // Account chooser button.
  UIBarButtonItem* _accountButton;

  // The currently represented folder.
  NSString* _driveFolderTitle;

  UITableViewDiffableDataSource<NSString*, DriveItemIdentifier*>*
      _diffableDataSource;
}

- (instancetype)init {
  self = [super initWithStyle:ChromeTableViewStyle()];
  if (self) {
    _status = DriveFileDownloadStatus::kNotStarted;
    _sortingOrder = DriveItemsSortingOrder::kDescending;
    _sortingType = DriveItemsSortingType::kModificationTime;
  }
  return self;
}

#pragma mark - UIViewController

- (void)viewDidLoad {
  [super viewDidLoad];

  [self configureSortButton];
  [self configureToolbar];

  self.navigationItem.rightBarButtonItem = [self configureRightBarButtonItem];

  // Add the search bar.
  self.navigationItem.searchController = [[UISearchController alloc] init];
  self.navigationItem.hidesSearchBarWhenScrolling = NO;
  self.navigationItem.preferredSearchBarPlacement =
      UINavigationItemSearchBarPlacementStacked;

  // Initialize the table view.
  self.tableView.backgroundColor =
      [UIColor colorNamed:kGroupedPrimaryBackgroundColor];
  self.tableView.tableHeaderView =
      [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, CGFLOAT_MIN)];

  self.navigationController.toolbarHidden = NO;

  __weak __typeof(self) weakSelf = self;
  auto cellProvider = ^UITableViewCell*(UITableView* tableView,
                                        NSIndexPath* indexPath,
                                        DriveItemIdentifier* itemIdentifier) {
    return [weakSelf cellForIndexPath:indexPath itemIdentifier:itemIdentifier];
  };
  _diffableDataSource =
      [[UITableViewDiffableDataSource alloc] initWithTableView:self.tableView
                                                  cellProvider:cellProvider];

  self.tableView.dataSource = _diffableDataSource;

  RegisterTableViewCell<TableViewDetailIconCell>(self.tableView);

  [self.mutator fetchDriveItemsForFolderID];
}

#pragma mark - DriveFilePickerConsumer

- (void)setSelectedUserIdentityEmail:(NSString*)selectedUserIdentityEmail {
  _selectedEmail = selectedUserIdentityEmail;
}

- (void)setCurrentDriveFolderTitle:(NSString*)currentDriveFolderTitle {
  _driveFolderTitle = currentDriveFolderTitle;
  UILabel* titleLabel = [[UILabel alloc] init];
  titleLabel.adjustsFontForContentSizeCategory = YES;
  titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
  titleLabel.text = _driveFolderTitle;
  titleLabel.textAlignment = NSTextAlignmentLeft;
  titleLabel.adjustsFontSizeToFitWidth = YES;
  titleLabel.minimumScaleFactor = 0.1;
  self.navigationItem.titleView = titleLabel;
}

#pragma mark - UI actions

- (void)confirmSelection {
  // TODO(crbug.com/344812396): Submit the file selection.
}

#pragma mark - Private

// Configures the toolbar with 3 buttons, filterButton <---->
// AccountButton(where the title is the user's email) <----> sortButton(which
// should not be enabled for the root of the navigation controller)
- (void)configureToolbar {
  UIImage* filterIcon = DefaultSymbolTemplateWithPointSize(
      kFilterSymbol, kSymbolAccessoryPointSize);

  // TODO(crbug.com/344812548): Add the action of the filter button.
  UIBarButtonItem* filterButton =
      [[UIBarButtonItem alloc] initWithImage:filterIcon
                                       style:UIBarButtonItemStylePlain
                                      target:self
                                      action:nil];
  filterButton.enabled = YES;

  UIImage* sortIcon = DefaultSymbolTemplateWithPointSize(
      kSortSymbol, kSymbolAccessoryPointSize);

  // TODO(crbug.com/344812548): Add the action of the sort button.
  _sortButton = [[UIBarButtonItem alloc]
      initWithImage:sortIcon
               menu:[UIMenu menuWithChildren:@[
                 _sortByNameAction, _sortByOpeningTimeAction,
                 _sortByModificationTimeAction
               ]]];
  _sortButton.enabled =
      self != self.navigationController.viewControllers.firstObject;

  UIBarButtonItem* spaceButton = [[UIBarButtonItem alloc]
      initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
                           target:nil
                           action:nil];
  [self setToolbarItems:@[
    filterButton, spaceButton, _accountButton, spaceButton, _sortButton
  ]
               animated:NO];
}

// Returns the right bar button based on the status.
- (UIBarButtonItem*)configureRightBarButtonItem {
  switch (_status) {
    case DriveFileDownloadStatus::kInProgress:
      return [self activityIndicatorButtonItem];
    case DriveFileDownloadStatus::kSuccess:
      return [self confirmButtonItem];
    case DriveFileDownloadStatus::kInterrupted:
    case DriveFileDownloadStatus::kFailed:
    case DriveFileDownloadStatus::kNotStarted: {
      UIBarButtonItem* rightBarButton = [self confirmButtonItem];
      rightBarButton.enabled = NO;
      return rightBarButton;
    }
  }
}

// Returns a button with the title `Confirm`.
- (UIBarButtonItem*)confirmButtonItem {
  UIBarButtonItem* confirmButton = [[UIBarButtonItem alloc]
      initWithTitle:l10n_util::GetNSString(IDS_IOS_DRIVE_FILE_PICKER_CONFIRM)
              style:UIBarButtonItemStyleDone
             target:self
             action:@selector(confirmSelection)];
  return confirmButton;
}

// Returns an activity indicator when the download is in progress.
- (UIBarButtonItem*)activityIndicatorButtonItem {
  UIActivityIndicatorView* activityIndicator = [[UIActivityIndicatorView alloc]
      initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
  [activityIndicator startAnimating];

  UIBarButtonItem* activityIndicatorButton =
      [[UIBarButtonItem alloc] initWithCustomView:activityIndicator];
  activityIndicatorButton.enabled = YES;
  return activityIndicatorButton;
}

// Configures the sort button by attaching a UIMenu of the actions to it.
- (void)configureSortButton {
  ActionFactory* actionFactory = [[ActionFactory alloc]
      initWithScenario:kMenuScenarioHistogramSortDriveItemsEntry];

  __weak __typeof(self) weakSelf = self;
  _sortByNameAction = [actionFactory actionToSortDriveItemsByNameWithBlock:^{
    [weakSelf turnOnSortByName];
    // TODO(crbug.com/344812548): Add the sorting request.
  }];
  _sortByModificationTimeAction =
      [actionFactory actionToSortDriveItemsByModificationTimeWithBlock:^{
        [weakSelf turnOnSortByModificationTime];
        // TODO(crbug.com/344812548): Add the sorting request.
      }];
  _sortByModificationTimeAction.state = UIMenuElementStateOn;
  _sortByModificationTimeAction.image =
      DefaultSymbolWithPointSize(kChevronDownSymbol, kSymbolAccessoryPointSize);
  _sortByOpeningTimeAction =
      [actionFactory actionToSortDriveItemsByOpeningTimeWithBlock:^{
        [weakSelf turnOnSortByOpeningTime];
        // TODO(crbug.com/344812548): Add the sorting request.
      }];
}

// Turns on the state of `_sortByNameAction` if it was previously off, if not,
// switches the sorting order.
- (void)turnOnSortByName {
  if (_sortingType == DriveItemsSortingType::kName) {
    [self switchSortingOrderForType:DriveItemsSortingType::kName];
  } else {
    [self selectSortingType:DriveItemsSortingType::kName];
  }
  _sortButton.menu = [_sortButton.menu menuByReplacingChildren:@[
    _sortByNameAction, _sortByOpeningTimeAction, _sortByModificationTimeAction
  ]];
}

// Turns on the state of `_sortByModificationTimeAction` if it was previously
// off, if not, switches the sorting order.
- (void)turnOnSortByModificationTime {
  if (_sortingType == DriveItemsSortingType::kModificationTime) {
    [self switchSortingOrderForType:DriveItemsSortingType::kModificationTime];
  } else {
    [self selectSortingType:DriveItemsSortingType::kModificationTime];
  }
  _sortButton.menu = [_sortButton.menu menuByReplacingChildren:@[
    _sortByNameAction, _sortByOpeningTimeAction, _sortByModificationTimeAction
  ]];
}

// Turns on the state of `_sortByOpeningTimeAction` if it was previously off, if
// not, switches the sorting order.
- (void)turnOnSortByOpeningTime {
  if (_sortingType == DriveItemsSortingType::kOpeningTime) {
    [self switchSortingOrderForType:DriveItemsSortingType::kOpeningTime];
  } else {
    [self selectSortingType:DriveItemsSortingType::kOpeningTime];
  }
  _sortButton.menu = [_sortButton.menu menuByReplacingChildren:@[
    _sortByNameAction, _sortByOpeningTimeAction, _sortByModificationTimeAction
  ]];
}

// Switches the recent sorting order and updates the actions icons accordinly.
- (void)switchSortingOrderForType:(DriveItemsSortingType)sortingType {
  UIAction* sortingAction = [self actionForSortingType:sortingType];
  switch (_sortingOrder) {
    case DriveItemsSortingOrder::kAscending:
      _sortingOrder = DriveItemsSortingOrder::kDescending;
      sortingAction.image = DefaultSymbolWithPointSize(
          kChevronDownSymbol, kSymbolAccessoryPointSize);
      break;
    case DriveItemsSortingOrder::kDescending:
      _sortingOrder = DriveItemsSortingOrder::kAscending;
      sortingAction.image = DefaultSymbolWithPointSize(
          kChevronUpSymbol, kSymbolAccessoryPointSize);
      break;
  }
}

// Resets the states and icons of the sorting actions.
- (void)resetSortingActionsStates {
  _sortByNameAction.state = UIMenuElementStateOff;
  _sortByNameAction.image = nil;
  _sortByOpeningTimeAction.state = UIMenuElementStateOff;
  _sortByOpeningTimeAction.image = nil;
  _sortByModificationTimeAction.state = UIMenuElementStateOff;
  _sortByModificationTimeAction.image = nil;
}

// Updates the status and icon of the action corresponding to a given sorting
// type.
- (void)selectSortingType:(DriveItemsSortingType)sortingType {
  UIAction* sortingAction = [self actionForSortingType:sortingType];
  [self resetSortingActionsStates];
  sortingAction.state = UIMenuElementStateOn;
  sortingAction.image =
      DefaultSymbolWithPointSize(kChevronDownSymbol, kSymbolAccessoryPointSize);
  _sortingType = sortingType;
  _sortingOrder = DriveItemsSortingOrder::kDescending;
}

// Returns the action corresponding to a given `sortingType`.
- (UIAction*)actionForSortingType:(DriveItemsSortingType)sortingType {
  switch (sortingType) {
    case DriveItemsSortingType::kName:
      return _sortByNameAction;
    case DriveItemsSortingType::kModificationTime:
      return _sortByModificationTimeAction;
    case DriveItemsSortingType::kOpeningTime:
      return _sortByOpeningTimeAction;
  }
}

// Deques and sets up a cell for a drive item.
- (UITableViewCell*)cellForIndexPath:(NSIndexPath*)indexPath
                      itemIdentifier:(DriveItemIdentifier*)itemIdentifier {
  TableViewDetailIconCell* cell =
      DequeueTableViewCell<TableViewDetailIconCell>(self.tableView);
  cell.selectionStyle = UITableViewCellSelectionStyleNone;
  cell.backgroundColor = [UIColor colorNamed:kGroupedSecondaryBackgroundColor];
  cell.userInteractionEnabled = YES;
  [cell.textLabel setText:itemIdentifier.title];
  cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

  [cell setIconImage:itemIdentifier.icon
            tintColor:nil
      backgroundColor:cell.backgroundColor
         cornerRadius:kCellIconCornerRadius];

  if (itemIdentifier.type == DriveItemType::kFile) {
    [cell setDetailText:itemIdentifier.creationDate];
    [cell setTextLayoutConstraintAxis:UILayoutConstraintAxisVertical];
    cell.accessoryType = UITableViewCellAccessoryNone;
  }
  return cell;
}

#pragma mark - DriveFilePickerConsumer

- (void)populateItems:(NSArray<DriveItemIdentifier*>*)driveItems {
  NSDiffableDataSourceSnapshot* snapshot =
      [[NSDiffableDataSourceSnapshot alloc] init];
  [snapshot
      appendSectionsWithIdentifiers:@[ @(SectionIdentifierDriveMainFolders) ]];
  [snapshot appendItemsWithIdentifiers:driveItems];
  [_diffableDataSource applySnapshot:snapshot animatingDifferences:YES];
}

- (void)setEmailsMenu:(UIMenu*)emailsMenu {
  _accountButton = [[UIBarButtonItem alloc] initWithTitle:_selectedEmail
                                                     menu:emailsMenu];
}

#pragma mark - UITableViewDelegate

- (void)tableView:(UITableView*)tableView
    didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
  DriveItemIdentifier* driveItem =
      [_diffableDataSource itemIdentifierForIndexPath:indexPath];
  if (driveItem.type == DriveItemType::kFolder) {
    [self.mutator selectDriveItem:driveItem];
  }
  // TODO(crbug.com/344812396): Add the download in case of files.
}

@end