| Index: ios/chrome/browser/ui/reading_list/reading_list_view_controller.mm
|
| diff --git a/ios/chrome/browser/ui/reading_list/reading_list_view_controller.mm b/ios/chrome/browser/ui/reading_list/reading_list_view_controller.mm
|
| deleted file mode 100644
|
| index 4a4861337fd18581a3e4f7164ce6a72b77d85957..0000000000000000000000000000000000000000
|
| --- a/ios/chrome/browser/ui/reading_list/reading_list_view_controller.mm
|
| +++ /dev/null
|
| @@ -1,1157 +0,0 @@
|
| -// Copyright 2016 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/reading_list/reading_list_view_controller.h"
|
| -
|
| -#include "base/bind.h"
|
| -#include "base/logging.h"
|
| -#import "base/mac/foundation_util.h"
|
| -#include "base/metrics/histogram_macros.h"
|
| -#include "base/metrics/user_metrics.h"
|
| -#include "base/metrics/user_metrics_action.h"
|
| -#include "base/strings/sys_string_conversions.h"
|
| -#include "base/time/time.h"
|
| -#include "components/reading_list/ios/reading_list_entry.h"
|
| -#include "components/reading_list/ios/reading_list_model.h"
|
| -#import "components/reading_list/ios/reading_list_model_bridge_observer.h"
|
| -#include "components/strings/grit/components_strings.h"
|
| -#include "components/url_formatter/url_formatter.h"
|
| -#include "ios/chrome/browser/reading_list/offline_url_utils.h"
|
| -#include "ios/chrome/browser/reading_list/reading_list_download_service.h"
|
| -#import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.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/favicon_view.h"
|
| -#import "ios/chrome/browser/ui/material_components/utils.h"
|
| -#import "ios/chrome/browser/ui/reading_list/reading_list_collection_view_item.h"
|
| -#import "ios/chrome/browser/ui/reading_list/reading_list_toolbar.h"
|
| -#import "ios/chrome/browser/ui/uikit_ui_util.h"
|
| -#include "ios/chrome/browser/ui/url_loader.h"
|
| -#include "ios/chrome/common/string_util.h"
|
| -#include "ios/chrome/grit/ios_strings.h"
|
| -#import "ios/third_party/material_components_ios/src/components/AppBar/src/MaterialAppBar.h"
|
| -#import "ios/third_party/material_components_ios/src/components/Palettes/src/MaterialPalettes.h"
|
| -#import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoFontLoader.h"
|
| -#include "ios/web/public/referrer.h"
|
| -#include "ios/web/public/web_state/web_state.h"
|
| -#include "net/base/network_change_notifier.h"
|
| -#include "ui/base/l10n/l10n_util_mac.h"
|
| -#include "ui/base/window_open_disposition.h"
|
| -
|
| -#if !defined(__has_feature) || !__has_feature(objc_arc)
|
| -#error "This file requires ARC support."
|
| -#endif
|
| -
|
| -namespace {
|
| -
|
| -NSString* const kEmptyReadingListBackgroundIcon = @"reading_list_empty_state";
|
| -NSString* const kBeginBoldMarker = @"BEGIN_BOLD_FONT";
|
| -NSString* const kEndBoldMarker = @"END_BOLD_FONT";
|
| -
|
| -// Background view constants.
|
| -const CGFloat kTextImageSpacing = 10;
|
| -const CGFloat kTextHorizontalMargin = 32;
|
| -const CGFloat kImageWidth = 60;
|
| -const CGFloat kImageHeight = 44;
|
| -const CGFloat kFontSize = 16;
|
| -
|
| -typedef NS_ENUM(NSInteger, SectionIdentifier) {
|
| - SectionIdentifierUnread = kSectionIdentifierEnumZero,
|
| - SectionIdentifierRead,
|
| -};
|
| -
|
| -typedef NS_ENUM(NSInteger, ItemType) {
|
| - ItemTypeUnreadHeader = kItemTypeEnumZero,
|
| - ItemTypeUnread,
|
| - ItemTypeReadHeader,
|
| - ItemTypeRead,
|
| -};
|
| -
|
| -// Typedef for a block taking a GURL as parameter and returning nothing.
|
| -typedef void (^EntryUpdater)(const GURL&);
|
| -
|
| -// Type for map used to sort ReadingListEntry by timestamp. Multiple entries can
|
| -// have the same timestamp.
|
| -using ItemsMapByDate = std::multimap<int64_t, ReadingListCollectionViewItem*>;
|
| -}
|
| -
|
| -@interface ReadingListViewController ()<ReadingListModelBridgeObserver> {
|
| - // Toolbar with the actions.
|
| - ReadingListToolbar* _toolbar;
|
| - // Action sheet presenting the subactions of the toolbar.
|
| - AlertCoordinator* _actionSheet;
|
| - std::unique_ptr<ReadingListModelBridge> _modelBridge;
|
| - UIView* _emptyCollectionBackground;
|
| -
|
| - // Whether the model has pending modifications.
|
| - BOOL _modelHasBeenModified;
|
| -}
|
| -
|
| -// Lazily instantiated.
|
| -@property(nonatomic, strong, readonly)
|
| - FaviconAttributesProvider* attributesProvider;
|
| -// Whether the Reading List Model (by opposition to the CollectionViewModel)
|
| -// modifications should be taken into account.
|
| -@property(nonatomic, assign) BOOL shouldMonitorModel;
|
| -
|
| -// Returns the UIView to be displayed when the reading list is empty.
|
| -- (UIView*)emptyCollectionBackground;
|
| -// Handles "Done" button touches.
|
| -- (void)donePressed;
|
| -// Loads all the items in all sections.
|
| -- (void)loadItems;
|
| -// Fills section |sectionIdentifier| with the items from |map| in reverse order
|
| -// of the map key.
|
| -- (void)loadItemsFromMap:(const ItemsMapByDate&)map
|
| - toSection:(SectionIdentifier)sectionIdentifier;
|
| -// Whether the model has changed.
|
| -- (BOOL)hasModelChanged;
|
| -// Returns whether there is a difference between the elements contained in the
|
| -// |sectionIdentifier| and those in the |map|.
|
| -- (BOOL)section:(SectionIdentifier)sectionIdentifier
|
| - isDifferentOfMap:(ItemsMapByDate&)map;
|
| -// Reloads the data if a changed occured during editing
|
| -- (void)applyPendingUpdates;
|
| -// Convenience method to create cell items for reading list entries.
|
| -- (ReadingListCollectionViewItem*)cellItemForReadingListEntry:
|
| - (const ReadingListEntry&)entry;
|
| -// Fills the |unread_map| and the |read_map| with the corresponding
|
| -// ReadingListCollectionViewItem from the readingListModel.
|
| -- (void)fillUnreadMap:(ItemsMapByDate&)unread_map
|
| - readMap:(ItemsMapByDate&)read_map;
|
| -// Returns whether there are elements in the section identified by
|
| -// |sectionIdentifier|.
|
| -- (BOOL)hasItemInSection:(SectionIdentifier)sectionIdentifier;
|
| -// Adds an empty background if needed.
|
| -- (void)collectionIsEmpty;
|
| -// Handles a long press.
|
| -- (void)handleLongPress:(UILongPressGestureRecognizer*)gestureRecognizer;
|
| -// Stops observing the ReadingListModel.
|
| -- (void)stopObservingReadingListModel;
|
| -// Updates the toolbar state according to the selected items.
|
| -- (void)updateToolbarState;
|
| -// Displays an action sheet to let the user choose to mark all the elements as
|
| -// read or as unread. Used when nothing is selected.
|
| -- (void)markAllItemsAs;
|
| -// Displays an action sheet to let the user choose to mark all the selected
|
| -// elements as read or as unread. Used if read and unread elements are selected.
|
| -- (void)markMixedItemsAs;
|
| -// Marks all items as read.
|
| -- (void)markAllRead;
|
| -// Marks all items as unread.
|
| -- (void)markAllUnread;
|
| -// Marks the selected items as read.
|
| -- (void)markItemsRead;
|
| -// Marks the selected items as unread.
|
| -- (void)markItemsUnread;
|
| -// Deletes all the read items.
|
| -- (void)deleteAllReadItems;
|
| -// Deletes all the selected items.
|
| -- (void)deleteSelectedItems;
|
| -// Initializes |_actionSheet| with |self| as base view controller, and the
|
| -// toolbar's mark button as anchor point.
|
| -- (void)initializeActionSheet;
|
| -// Exits the editing mode and update the toolbar state with animation.
|
| -- (void)exitEditingModeAnimated:(BOOL)animated;
|
| -// Applies |updater| to the URL of every cell in the section |identifier|. The
|
| -// updates are done in reverse order of the cells in the section to keep the
|
| -// order. The monitoring of the model updates are suspended during this time.
|
| -- (void)updateItemsInSectionIdentifier:(SectionIdentifier)identifier
|
| - usingEntryUpdater:(EntryUpdater)updater;
|
| -// Applies |updater| to the URL of every element in |indexPaths|. The updates
|
| -// are done in reverse order |indexPaths| to keep the order. The monitoring of
|
| -// the model updates are suspended during this time.
|
| -- (void)updateIndexPaths:(NSArray<NSIndexPath*>*)indexPaths
|
| - usingEntryUpdater:(EntryUpdater)updater;
|
| -// Logs the deletions histograms for the entry with |url|.
|
| -- (void)logDeletionHistogramsForEntry:(const GURL&)url;
|
| -// Move all the items from |sourceSectionIdentifier| to
|
| -// |destinationSectionIdentifier| and removes the empty section from the
|
| -// collection.
|
| -- (void)moveItemsFromSection:(SectionIdentifier)sourceSectionIdentifier
|
| - toSection:(SectionIdentifier)destinationSectionIdentifier;
|
| -// Move the currently selected elements to |sectionIdentifier| and removes the
|
| -// empty sections.
|
| -- (void)moveSelectedItems:(NSArray*)sortedIndexPaths
|
| - toSection:(SectionIdentifier)sectionIdentifier;
|
| -// Makes sure |sectionIdentifier| exists with the correct header.
|
| -// Returns the index of the new section in the collection view; NSIntegerMax if
|
| -// no section has been created.
|
| -- (NSInteger)initializeSection:(SectionIdentifier)sectionIdentifier;
|
| -// Returns the header for the |sectionIdentifier|.
|
| -- (CollectionViewTextItem*)headerForSection:
|
| - (SectionIdentifier)sectionIdentifier;
|
| -// Removes the empty sections from the collection and the model.
|
| -- (void)removeEmptySections;
|
| -
|
| -@end
|
| -
|
| -@implementation ReadingListViewController
|
| -@synthesize readingListModel = _readingListModel;
|
| -@synthesize largeIconService = _largeIconService;
|
| -@synthesize readingListDownloadService = _readingListDownloadService;
|
| -@synthesize attributesProvider = _attributesProvider;
|
| -@synthesize delegate = _delegate;
|
| -@synthesize shouldMonitorModel = _shouldMonitorModel;
|
| -#pragma mark lifecycle
|
| -
|
| -- (instancetype)initWithModel:(ReadingListModel*)model
|
| - largeIconService:(favicon::LargeIconService*)largeIconService
|
| - readingListDownloadService:
|
| - (ReadingListDownloadService*)readingListDownloadService
|
| - toolbar:(ReadingListToolbar*)toolbar {
|
| - self = [super initWithStyle:CollectionViewControllerStyleAppBar];
|
| - if (self) {
|
| - DCHECK(model);
|
| - _toolbar = toolbar;
|
| -
|
| - _readingListModel = model;
|
| - _largeIconService = largeIconService;
|
| - _readingListDownloadService = readingListDownloadService;
|
| - _emptyCollectionBackground = [self emptyCollectionBackground];
|
| -
|
| - _shouldMonitorModel = YES;
|
| - _modelHasBeenModified = NO;
|
| -
|
| - _modelBridge.reset(new ReadingListModelBridge(self, model));
|
| - }
|
| - return self;
|
| -}
|
| -
|
| -#pragma mark - properties
|
| -
|
| -- (FaviconAttributesProvider*)attributesProvider {
|
| - if (_attributesProvider) {
|
| - return _attributesProvider;
|
| - }
|
| -
|
| - _attributesProvider = [[FaviconAttributesProvider alloc]
|
| - initWithFaviconSize:kFaviconPreferredSize
|
| - minFaviconSize:kFaviconMinSize
|
| - largeIconService:self.largeIconService];
|
| - return _attributesProvider;
|
| -}
|
| -
|
| -- (void)setToolbarState:(ReadingListToolbarState)toolbarState {
|
| - [_toolbar setState:toolbarState];
|
| -}
|
| -
|
| -- (void)setDelegate:(id<ReadingListViewControllerDelegate>)delegate {
|
| - _delegate = delegate;
|
| - if (self.readingListModel->loaded())
|
| - [delegate readingListViewController:self
|
| - hasItems:(self.readingListModel->size() > 0)];
|
| -}
|
| -
|
| -#pragma mark - UIViewController
|
| -
|
| -- (void)viewDidLoad {
|
| - [super viewDidLoad];
|
| -
|
| - self.title = l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_READING_LIST);
|
| -
|
| - // Add "Done" button.
|
| - UIBarButtonItem* doneItem = [[UIBarButtonItem alloc]
|
| - initWithTitle:l10n_util::GetNSString(IDS_IOS_READING_LIST_DONE_BUTTON)
|
| - style:UIBarButtonItemStylePlain
|
| - target:self
|
| - action:@selector(donePressed)];
|
| - doneItem.accessibilityIdentifier = @"Done";
|
| - self.navigationItem.rightBarButtonItem = doneItem;
|
| -
|
| - // Customize collection view settings.
|
| - self.styler.cellStyle = MDCCollectionViewCellStyleCard;
|
| - self.styler.separatorInset = UIEdgeInsetsMake(0, 16, 0, 16);
|
| -
|
| - UILongPressGestureRecognizer* longPressRecognizer =
|
| - [[UILongPressGestureRecognizer alloc]
|
| - initWithTarget:self
|
| - action:@selector(handleLongPress:)];
|
| - longPressRecognizer.numberOfTouchesRequired = 1;
|
| - [self.collectionView addGestureRecognizer:longPressRecognizer];
|
| -}
|
| -
|
| -#pragma mark - UICollectionViewDelegate
|
| -
|
| -- (UICollectionReusableView*)collectionView:(UICollectionView*)collectionView
|
| - viewForSupplementaryElementOfKind:(NSString*)kind
|
| - atIndexPath:(NSIndexPath*)indexPath {
|
| - UICollectionReusableView* cell = [super collectionView:collectionView
|
| - viewForSupplementaryElementOfKind:kind
|
| - atIndexPath:indexPath];
|
| - MDCCollectionViewTextCell* textCell =
|
| - base::mac::ObjCCast<MDCCollectionViewTextCell>(cell);
|
| - if (textCell) {
|
| - textCell.textLabel.textColor = [[MDCPalette greyPalette] tint500];
|
| - }
|
| - return cell;
|
| -};
|
| -
|
| -- (void)collectionView:(UICollectionView*)collectionView
|
| - didSelectItemAtIndexPath:(NSIndexPath*)indexPath {
|
| - [super collectionView:collectionView didSelectItemAtIndexPath:indexPath];
|
| -
|
| - if (self.editor.editing) {
|
| - [self updateToolbarState];
|
| - } else {
|
| - ReadingListCollectionViewItem* readingListItem =
|
| - base::mac::ObjCCastStrict<ReadingListCollectionViewItem>(
|
| - [self.collectionViewModel itemAtIndexPath:indexPath]);
|
| -
|
| - [self.delegate readingListViewController:self openItem:readingListItem];
|
| - }
|
| -}
|
| -
|
| -- (void)collectionView:(UICollectionView*)collectionView
|
| - didDeselectItemAtIndexPath:(NSIndexPath*)indexPath {
|
| - [super collectionView:collectionView didDeselectItemAtIndexPath:indexPath];
|
| - if (self.editor.editing) {
|
| - // When deselecting an item, if we are editing, we want to update the
|
| - // toolbar base on the selected items.
|
| - [self updateToolbarState];
|
| - }
|
| -}
|
| -
|
| -#pragma mark - MDCCollectionViewController
|
| -
|
| -- (void)updateFooterInfoBarIfNecessary {
|
| - // No-op. This prevents the default infobar from showing.
|
| - // TODO(crbug.com/653547): Remove this once the MDC adds an option for
|
| - // preventing the infobar from showing.
|
| -}
|
| -
|
| -#pragma mark - MDCCollectionViewStylingDelegate
|
| -
|
| -- (CGFloat)collectionView:(UICollectionView*)collectionView
|
| - cellHeightAtIndexPath:(NSIndexPath*)indexPath {
|
| - NSInteger type = [self.collectionViewModel itemTypeForIndexPath:indexPath];
|
| - if (type == ItemTypeUnread || type == ItemTypeRead)
|
| - return MDCCellDefaultTwoLineHeight;
|
| - else
|
| - return MDCCellDefaultOneLineHeight;
|
| -}
|
| -
|
| -#pragma mark - MDCCollectionViewEditingDelegate
|
| -
|
| -- (BOOL)collectionViewAllowsEditing:(nonnull UICollectionView*)collectionView {
|
| - return YES;
|
| -}
|
| -
|
| -#pragma mark - ReadingListModelBridgeObserver
|
| -
|
| -- (void)readingListModelLoaded:(const ReadingListModel*)model {
|
| - [self loadModel];
|
| - UMA_HISTOGRAM_COUNTS_1000("ReadingList.Unread.Number", model->unread_size());
|
| - UMA_HISTOGRAM_COUNTS_1000("ReadingList.Read.Number",
|
| - model->size() - model->unread_size());
|
| - if ([self isViewLoaded]) {
|
| - [self.collectionView reloadData];
|
| - }
|
| -}
|
| -
|
| -- (void)readingListModelDidApplyChanges:(const ReadingListModel*)model {
|
| - if (!self.shouldMonitorModel) {
|
| - return;
|
| - }
|
| -
|
| - // If we are editing and monitoring the model updates, set a flag to reload
|
| - // the data at the end of the editing.
|
| - if ([self.editor isEditing]) {
|
| - _modelHasBeenModified = YES;
|
| - return;
|
| - }
|
| -
|
| - // Ignore model updates when the view controller is doing batch updates.
|
| - if (model->IsPerformingBatchUpdates()) {
|
| - return;
|
| - }
|
| -
|
| - if ([self hasModelChanged])
|
| - [self reloadData];
|
| -}
|
| -
|
| -- (void)readingListModelCompletedBatchUpdates:(const ReadingListModel*)model {
|
| - if (!self.shouldMonitorModel) {
|
| - return;
|
| - }
|
| -
|
| - if ([self hasModelChanged])
|
| - [self reloadData];
|
| -}
|
| -
|
| -#pragma mark - Public methods
|
| -
|
| -- (void)reloadData {
|
| - [self loadModel];
|
| - if ([self isViewLoaded]) {
|
| - [self.collectionView reloadData];
|
| - }
|
| -}
|
| -
|
| -- (void)willBeDismissed {
|
| - _readingListModel->MarkAllSeen();
|
| - // Reset observer to prevent further model update notifications.
|
| - [self stopObservingReadingListModel];
|
| - [_actionSheet stop];
|
| -}
|
| -
|
| -#pragma mark - Private methods
|
| -
|
| -- (UIView*)emptyCollectionBackground {
|
| - UIView* emptyCollectionBackground = [[UIView alloc] initWithFrame:CGRectZero];
|
| -
|
| - NSString* rawText = nil;
|
| - if (IsCompact())
|
| - rawText = l10n_util::GetNSString(IDS_IOS_READING_LIST_EMPTY_MESSAGE_IPHONE);
|
| - else
|
| - rawText = l10n_util::GetNSString(IDS_IOS_READING_LIST_EMPTY_MESSAGE_IPAD);
|
| -
|
| - rawText = [[rawText stringByAppendingString:@"\n\n"]
|
| - stringByAppendingString:l10n_util::GetNSString(
|
| - IDS_IOS_READING_LIST_EMPTY_OFFLINE)];
|
| -
|
| - NSRange tagRange;
|
| - NSString* untaggedString =
|
| - ParseStringWithTag(rawText, &tagRange, kBeginBoldMarker, kEndBoldMarker);
|
| -
|
| - NSMutableParagraphStyle* paragraphStyle =
|
| - [[NSMutableParagraphStyle alloc] init];
|
| - // This headIndent is needed to prevent a flicker of the text on iPhone 6+
|
| - // when the user scrolls the empty collection.
|
| - paragraphStyle.headIndent = 1;
|
| - NSDictionary* attributes = @{
|
| - NSFontAttributeName :
|
| - [[MDFRobotoFontLoader sharedInstance] regularFontOfSize:kFontSize],
|
| - NSForegroundColorAttributeName : [[MDCPalette greyPalette] tint700],
|
| - NSParagraphStyleAttributeName : paragraphStyle
|
| - };
|
| -
|
| - NSMutableAttributedString* baseAttributedString =
|
| - [[NSMutableAttributedString alloc] initWithString:untaggedString
|
| - attributes:attributes];
|
| -
|
| - [baseAttributedString addAttribute:NSFontAttributeName
|
| - value:[[MDFRobotoFontLoader sharedInstance]
|
| - mediumFontOfSize:kFontSize]
|
| - range:tagRange];
|
| -
|
| - UILabel* label = [[UILabel alloc] initWithFrame:CGRectZero];
|
| - label.attributedText = baseAttributedString;
|
| - label.numberOfLines = 0;
|
| - label.textAlignment = NSTextAlignmentCenter;
|
| - [label setTranslatesAutoresizingMaskIntoConstraints:NO];
|
| - [emptyCollectionBackground addSubview:label];
|
| -
|
| - UIImageView* imageView = [[UIImageView alloc] init];
|
| - imageView.image = [UIImage imageNamed:kEmptyReadingListBackgroundIcon];
|
| - [imageView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
| - [emptyCollectionBackground addSubview:imageView];
|
| -
|
| - [NSLayoutConstraint activateConstraints:@[
|
| - [[imageView heightAnchor] constraintEqualToConstant:kImageHeight],
|
| - [[imageView widthAnchor] constraintEqualToConstant:kImageWidth],
|
| - [[emptyCollectionBackground centerXAnchor]
|
| - constraintEqualToAnchor:label.centerXAnchor],
|
| - [[emptyCollectionBackground centerXAnchor]
|
| - constraintEqualToAnchor:imageView.centerXAnchor],
|
| - [label.topAnchor constraintEqualToAnchor:imageView.bottomAnchor
|
| - constant:kTextImageSpacing]
|
| - ]];
|
| -
|
| - // Position the top of the image at 40% from the top.
|
| - NSLayoutConstraint* verticalAlignment =
|
| - [NSLayoutConstraint constraintWithItem:imageView
|
| - attribute:NSLayoutAttributeTop
|
| - relatedBy:NSLayoutRelationEqual
|
| - toItem:emptyCollectionBackground
|
| - attribute:NSLayoutAttributeBottom
|
| - multiplier:0.4
|
| - constant:0];
|
| - [emptyCollectionBackground addConstraints:@[ verticalAlignment ]];
|
| -
|
| - ApplyVisualConstraintsWithMetrics(@[ @"H:|-(margin)-[textLabel]-(margin)-|" ],
|
| - @{ @"textLabel" : label },
|
| - @{ @"margin" : @(kTextHorizontalMargin) });
|
| -
|
| - return emptyCollectionBackground;
|
| -}
|
| -
|
| -- (void)donePressed {
|
| - if ([self.editor isEditing]) {
|
| - [self exitEditingModeAnimated:NO];
|
| - }
|
| - [self dismiss];
|
| -}
|
| -
|
| -- (void)dismiss {
|
| - [self.delegate dismissReadingListViewController:self];
|
| -}
|
| -
|
| -- (void)loadModel {
|
| - [super loadModel];
|
| -
|
| - if (self.readingListModel->size() == 0) {
|
| - [self collectionIsEmpty];
|
| - } else {
|
| - self.collectionView.alwaysBounceVertical = YES;
|
| - [self loadItems];
|
| - self.collectionView.backgroundView = nil;
|
| - [self.delegate readingListViewController:self hasItems:YES];
|
| - }
|
| -}
|
| -
|
| -- (void)loadItemsFromMap:(const ItemsMapByDate&)map
|
| - toSection:(SectionIdentifier)sectionIdentifier {
|
| - if (map.size() == 0) {
|
| - return;
|
| - }
|
| - CollectionViewModel* model = self.collectionViewModel;
|
| - [model addSectionWithIdentifier:sectionIdentifier];
|
| - [model setHeader:[self headerForSection:sectionIdentifier]
|
| - forSectionWithIdentifier:sectionIdentifier];
|
| - // Reverse iterate to add newer entries at the top.
|
| - ItemsMapByDate::const_reverse_iterator iterator = map.rbegin();
|
| - for (; iterator != map.rend(); iterator++) {
|
| - [model addItem:iterator->second toSectionWithIdentifier:sectionIdentifier];
|
| - }
|
| -}
|
| -
|
| -- (void)loadItems {
|
| - ItemsMapByDate read_map;
|
| - ItemsMapByDate unread_map;
|
| - [self fillUnreadMap:unread_map readMap:read_map];
|
| - [self loadItemsFromMap:unread_map toSection:SectionIdentifierUnread];
|
| - [self loadItemsFromMap:read_map toSection:SectionIdentifierRead];
|
| -
|
| - BOOL hasRead = read_map.size() > 0;
|
| - [_toolbar setHasReadItem:hasRead];
|
| -}
|
| -
|
| -- (void)fillUnreadMap:(ItemsMapByDate&)unread_map
|
| - readMap:(ItemsMapByDate&)read_map {
|
| - for (const auto& url : self.readingListModel->Keys()) {
|
| - const ReadingListEntry* entry = self.readingListModel->GetEntryByURL(url);
|
| - ReadingListCollectionViewItem* item =
|
| - [self cellItemForReadingListEntry:*entry];
|
| - if (entry->IsRead()) {
|
| - read_map.insert(std::make_pair(entry->UpdateTime(), item));
|
| - } else {
|
| - unread_map.insert(std::make_pair(entry->UpdateTime(), item));
|
| - }
|
| - }
|
| -}
|
| -
|
| -- (void)applyPendingUpdates {
|
| - if (_modelHasBeenModified) {
|
| - [self reloadData];
|
| - }
|
| -}
|
| -
|
| -- (BOOL)hasModelChanged {
|
| - ItemsMapByDate read_map;
|
| - ItemsMapByDate unread_map;
|
| - [self fillUnreadMap:unread_map readMap:read_map];
|
| -
|
| - if ([self section:SectionIdentifierRead isDifferentOfMap:read_map])
|
| - return YES;
|
| - if ([self section:SectionIdentifierUnread isDifferentOfMap:unread_map])
|
| - return YES;
|
| -
|
| - return NO;
|
| -}
|
| -
|
| -- (BOOL)section:(SectionIdentifier)sectionIdentifier
|
| - isDifferentOfMap:(ItemsMapByDate&)map {
|
| - if (![self.collectionViewModel
|
| - hasSectionForSectionIdentifier:sectionIdentifier]) {
|
| - return !map.empty();
|
| - }
|
| -
|
| - NSArray* items =
|
| - [self.collectionViewModel itemsInSectionWithIdentifier:sectionIdentifier];
|
| - if ([items count] != map.size())
|
| - return YES;
|
| -
|
| - NSInteger index = 0;
|
| - ItemsMapByDate::const_reverse_iterator iterator = map.rbegin();
|
| - for (; iterator != map.rend(); iterator++) {
|
| - ReadingListCollectionViewItem* oldItem =
|
| - base::mac::ObjCCastStrict<ReadingListCollectionViewItem>(items[index]);
|
| - ReadingListCollectionViewItem* newItem = iterator->second;
|
| - if (oldItem.url == newItem.url) {
|
| - oldItem.text = newItem.text;
|
| - oldItem.distillationState = newItem.distillationState;
|
| - oldItem.faviconPageURL = newItem.faviconPageURL;
|
| - }
|
| - if (![oldItem isEqual:newItem]) {
|
| - return YES;
|
| - }
|
| - index++;
|
| - }
|
| - return NO;
|
| -}
|
| -
|
| -- (ReadingListCollectionViewItem*)cellItemForReadingListEntry:
|
| - (const ReadingListEntry&)entry {
|
| - GURL url = entry.URL();
|
| - ReadingListCollectionViewItem* item = [[ReadingListCollectionViewItem alloc]
|
| - initWithType:entry.IsRead() ? ItemTypeRead : ItemTypeUnread
|
| - attributesProvider:self.attributesProvider
|
| - url:url
|
| - distillationState:entry.DistilledState()];
|
| - base::string16 urlString = url_formatter::FormatUrl(url);
|
| - item.text = base::SysUTF8ToNSString(entry.Title());
|
| - item.detailText = base::SysUTF16ToNSString(urlString);
|
| - item.faviconPageURL =
|
| - entry.DistilledURL().is_valid() ? entry.DistilledURL() : url;
|
| - return item;
|
| -}
|
| -
|
| -- (BOOL)hasItemInSection:(SectionIdentifier)sectionIdentifier {
|
| - if (![self.collectionViewModel
|
| - hasSectionForSectionIdentifier:sectionIdentifier]) {
|
| - // No section.
|
| - return NO;
|
| - }
|
| -
|
| - NSInteger section =
|
| - [self.collectionViewModel sectionForSectionIdentifier:sectionIdentifier];
|
| - NSInteger numberOfItems =
|
| - [self.collectionViewModel numberOfItemsInSection:section];
|
| -
|
| - return numberOfItems > 0;
|
| -}
|
| -
|
| -- (void)collectionIsEmpty {
|
| - if (self.collectionView.backgroundView) {
|
| - return;
|
| - }
|
| - // The collection is empty, add background.
|
| - self.collectionView.alwaysBounceVertical = NO;
|
| - self.collectionView.backgroundView = _emptyCollectionBackground;
|
| - [self.delegate readingListViewController:self hasItems:NO];
|
| -}
|
| -
|
| -- (void)handleLongPress:(UILongPressGestureRecognizer*)gestureRecognizer {
|
| - if (self.editor.editing ||
|
| - gestureRecognizer.state != UIGestureRecognizerStateBegan) {
|
| - return;
|
| - }
|
| -
|
| - CGPoint touchLocation =
|
| - [gestureRecognizer locationOfTouch:0 inView:self.collectionView];
|
| - NSIndexPath* touchedItemIndexPath =
|
| - [self.collectionView indexPathForItemAtPoint:touchLocation];
|
| - if (!touchedItemIndexPath ||
|
| - ![self.collectionViewModel hasItemAtIndexPath:touchedItemIndexPath]) {
|
| - // Make sure there is an item at this position.
|
| - return;
|
| - }
|
| - CollectionViewItem* touchedItem =
|
| - [self.collectionViewModel itemAtIndexPath:touchedItemIndexPath];
|
| -
|
| - if (touchedItem == [self.collectionViewModel
|
| - headerForSection:touchedItemIndexPath.section] ||
|
| - ![touchedItem isKindOfClass:[ReadingListCollectionViewItem class]]) {
|
| - // Do not trigger context menu on headers.
|
| - return;
|
| - }
|
| -
|
| - ReadingListCollectionViewItem* readingListItem =
|
| - base::mac::ObjCCastStrict<ReadingListCollectionViewItem>(touchedItem);
|
| -
|
| - [self.delegate readingListViewController:self
|
| - displayContextMenuForItem:readingListItem
|
| - atPoint:touchLocation];
|
| -}
|
| -
|
| -- (void)stopObservingReadingListModel {
|
| - _modelBridge.reset();
|
| -}
|
| -
|
| -#pragma mark - ReadingListToolbarDelegate
|
| -
|
| -- (void)markPressed {
|
| - if (![self.editor isEditing]) {
|
| - return;
|
| - }
|
| - switch ([_toolbar state]) {
|
| - case NoneSelected:
|
| - [self markAllItemsAs];
|
| - break;
|
| - case OnlyUnreadSelected:
|
| - [self markItemsRead];
|
| - break;
|
| - case OnlyReadSelected:
|
| - [self markItemsUnread];
|
| - break;
|
| - case MixedItemsSelected:
|
| - [self markMixedItemsAs];
|
| - break;
|
| - }
|
| -}
|
| -
|
| -- (void)deletePressed {
|
| - if (![self.editor isEditing]) {
|
| - return;
|
| - }
|
| - if ([_toolbar state] == NoneSelected) {
|
| - [self deleteAllReadItems];
|
| - } else {
|
| - [self deleteSelectedItems];
|
| - }
|
| -}
|
| -- (void)enterEditingModePressed {
|
| - if ([self.editor isEditing]) {
|
| - return;
|
| - }
|
| - self.toolbarState = NoneSelected;
|
| - [self.editor setEditing:YES animated:YES];
|
| - [_toolbar setEditing:YES];
|
| -}
|
| -
|
| -- (void)exitEditingModePressed {
|
| - if (![self.editor isEditing]) {
|
| - return;
|
| - }
|
| - [self exitEditingModeAnimated:YES];
|
| -}
|
| -
|
| -#pragma mark - Private methods - Toolbar
|
| -
|
| -- (void)updateToolbarState {
|
| - BOOL readSelected = NO;
|
| - BOOL unreadSelected = NO;
|
| -
|
| - if ([self.collectionView.indexPathsForSelectedItems count] == 0) {
|
| - // No entry selected.
|
| - self.toolbarState = NoneSelected;
|
| - return;
|
| - }
|
| -
|
| - // Sections for section identifiers.
|
| - NSInteger sectionRead = NSNotFound;
|
| - NSInteger sectionUnread = NSNotFound;
|
| -
|
| - if ([self hasItemInSection:SectionIdentifierRead]) {
|
| - sectionRead = [self.collectionViewModel
|
| - sectionForSectionIdentifier:SectionIdentifierRead];
|
| - }
|
| - if ([self hasItemInSection:SectionIdentifierUnread]) {
|
| - sectionUnread = [self.collectionViewModel
|
| - sectionForSectionIdentifier:SectionIdentifierUnread];
|
| - }
|
| -
|
| - // Check selected sections.
|
| - for (NSIndexPath* index in self.collectionView.indexPathsForSelectedItems) {
|
| - if (index.section == sectionRead) {
|
| - readSelected = YES;
|
| - } else if (index.section == sectionUnread) {
|
| - unreadSelected = YES;
|
| - }
|
| - }
|
| -
|
| - // Update toolbar state.
|
| - if (readSelected) {
|
| - if (unreadSelected) {
|
| - // Read and Unread selected.
|
| - self.toolbarState = MixedItemsSelected;
|
| - } else {
|
| - // Read selected.
|
| - self.toolbarState = OnlyReadSelected;
|
| - }
|
| - } else if (unreadSelected) {
|
| - // Unread selected.
|
| - self.toolbarState = OnlyUnreadSelected;
|
| - }
|
| -}
|
| -
|
| -- (void)markAllItemsAs {
|
| - [self initializeActionSheet];
|
| - __weak ReadingListViewController* weakSelf = self;
|
| - [_actionSheet addItemWithTitle:l10n_util::GetNSStringWithFixup(
|
| - IDS_IOS_READING_LIST_MARK_ALL_READ_ACTION)
|
| - action:^{
|
| - [weakSelf markAllRead];
|
| - }
|
| - style:UIAlertActionStyleDefault];
|
| - [_actionSheet
|
| - addItemWithTitle:l10n_util::GetNSStringWithFixup(
|
| - IDS_IOS_READING_LIST_MARK_ALL_UNREAD_ACTION)
|
| - action:^{
|
| - [weakSelf markAllUnread];
|
| - }
|
| - style:UIAlertActionStyleDefault];
|
| - [_actionSheet start];
|
| -}
|
| -
|
| -- (void)markMixedItemsAs {
|
| - [self initializeActionSheet];
|
| - __weak ReadingListViewController* weakSelf = self;
|
| - [_actionSheet addItemWithTitle:l10n_util::GetNSStringWithFixup(
|
| - IDS_IOS_READING_LIST_MARK_READ_BUTTON)
|
| - action:^{
|
| - [weakSelf markItemsRead];
|
| - }
|
| - style:UIAlertActionStyleDefault];
|
| - [_actionSheet addItemWithTitle:l10n_util::GetNSStringWithFixup(
|
| - IDS_IOS_READING_LIST_MARK_UNREAD_BUTTON)
|
| - action:^{
|
| - [weakSelf markItemsUnread];
|
| - }
|
| - style:UIAlertActionStyleDefault];
|
| - [_actionSheet start];
|
| -}
|
| -
|
| -- (void)initializeActionSheet {
|
| - _actionSheet = [_toolbar actionSheetForMarkWithBaseViewController:self];
|
| -
|
| - [_actionSheet addItemWithTitle:l10n_util::GetNSStringWithFixup(IDS_CANCEL)
|
| - action:nil
|
| - style:UIAlertActionStyleCancel];
|
| -}
|
| -
|
| -- (void)markAllRead {
|
| - if (![self.editor isEditing]) {
|
| - return;
|
| - }
|
| - if (![self hasItemInSection:SectionIdentifierUnread]) {
|
| - [self exitEditingModeAnimated:YES];
|
| - return;
|
| - }
|
| -
|
| - [self updateItemsInSectionIdentifier:SectionIdentifierUnread
|
| - usingEntryUpdater:^(const GURL& url) {
|
| - [self readingListModel]->SetReadStatus(url, true);
|
| - }];
|
| - [self exitEditingModeAnimated:YES];
|
| - [self moveItemsFromSection:SectionIdentifierUnread
|
| - toSection:SectionIdentifierRead];
|
| -}
|
| -
|
| -- (void)markAllUnread {
|
| - if (![self hasItemInSection:SectionIdentifierRead]) {
|
| - [self exitEditingModeAnimated:YES];
|
| - return;
|
| - }
|
| -
|
| - [self updateItemsInSectionIdentifier:SectionIdentifierRead
|
| - usingEntryUpdater:^(const GURL& url) {
|
| - [self readingListModel]->SetReadStatus(url, false);
|
| - }];
|
| - [self exitEditingModeAnimated:YES];
|
| - [self moveItemsFromSection:SectionIdentifierRead
|
| - toSection:SectionIdentifierUnread];
|
| -}
|
| -
|
| -- (void)markItemsRead {
|
| - base::RecordAction(base::UserMetricsAction("MobileReadingListMarkRead"));
|
| - NSArray* sortedIndexPaths = [self.collectionView.indexPathsForSelectedItems
|
| - sortedArrayUsingSelector:@selector(compare:)];
|
| - [self updateIndexPaths:sortedIndexPaths
|
| - usingEntryUpdater:^(const GURL& url) {
|
| - [self readingListModel]->SetReadStatus(url, true);
|
| - }];
|
| -
|
| - [self exitEditingModeAnimated:YES];
|
| - [self moveSelectedItems:sortedIndexPaths toSection:SectionIdentifierRead];
|
| -}
|
| -
|
| -- (void)markItemsUnread {
|
| - base::RecordAction(base::UserMetricsAction("MobileReadingListMarkUnread"));
|
| - NSArray* sortedIndexPaths = [self.collectionView.indexPathsForSelectedItems
|
| - sortedArrayUsingSelector:@selector(compare:)];
|
| - [self updateIndexPaths:sortedIndexPaths
|
| - usingEntryUpdater:^(const GURL& url) {
|
| - [self readingListModel]->SetReadStatus(url, false);
|
| - }];
|
| -
|
| - [self exitEditingModeAnimated:YES];
|
| - [self moveSelectedItems:sortedIndexPaths toSection:SectionIdentifierUnread];
|
| -}
|
| -
|
| -- (void)deleteAllReadItems {
|
| - base::RecordAction(base::UserMetricsAction("MobileReadingListDeleteRead"));
|
| - if (![self hasItemInSection:SectionIdentifierRead]) {
|
| - [self exitEditingModeAnimated:YES];
|
| - return;
|
| - }
|
| -
|
| - [self updateItemsInSectionIdentifier:SectionIdentifierRead
|
| - usingEntryUpdater:^(const GURL& url) {
|
| - [self logDeletionHistogramsForEntry:url];
|
| - [self readingListModel]->RemoveEntryByURL(url);
|
| - }];
|
| -
|
| - [self exitEditingModeAnimated:YES];
|
| - [self.collectionView performBatchUpdates:^{
|
| - NSInteger readSection = [self.collectionViewModel
|
| - sectionForSectionIdentifier:SectionIdentifierRead];
|
| - [self.collectionView
|
| - deleteSections:[NSIndexSet indexSetWithIndex:readSection]];
|
| - [self.collectionViewModel
|
| - removeSectionWithIdentifier:SectionIdentifierRead];
|
| - }
|
| - completion:^(BOOL) {
|
| - // Reload data to take into account possible sync events.
|
| - [self applyPendingUpdates];
|
| - }];
|
| - // As we modified the section in the batch update block, remove the section in
|
| - // another block.
|
| - [self removeEmptySections];
|
| -}
|
| -
|
| -- (void)deleteSelectedItems {
|
| - NSArray* indexPaths = [self.collectionView.indexPathsForSelectedItems copy];
|
| - [self updateIndexPaths:indexPaths
|
| - usingEntryUpdater:^(const GURL& url) {
|
| - [self logDeletionHistogramsForEntry:url];
|
| - [self readingListModel]->RemoveEntryByURL(url);
|
| - }];
|
| -
|
| - [self exitEditingModeAnimated:YES];
|
| -
|
| - [self.collectionView performBatchUpdates:^{
|
| - [self collectionView:self.collectionView
|
| - willDeleteItemsAtIndexPaths:indexPaths];
|
| -
|
| - [self.collectionView deleteItemsAtIndexPaths:indexPaths];
|
| - }
|
| - completion:^(BOOL) {
|
| - // Reload data to take into account possible sync events.
|
| - [self applyPendingUpdates];
|
| - }];
|
| - // As we modified the section in the batch update block, remove the section in
|
| - // another block.
|
| - [self removeEmptySections];
|
| -}
|
| -
|
| -- (void)updateItemsInSectionIdentifier:(SectionIdentifier)identifier
|
| - usingEntryUpdater:(EntryUpdater)updater {
|
| - self.shouldMonitorModel = NO;
|
| - auto token = self.readingListModel->BeginBatchUpdates();
|
| - NSArray* readItems =
|
| - [self.collectionViewModel itemsInSectionWithIdentifier:identifier];
|
| - // Read the objects in reverse order to keep the order (last modified first).
|
| - for (id item in [readItems reverseObjectEnumerator]) {
|
| - ReadingListCollectionViewItem* readingListItem =
|
| - base::mac::ObjCCastStrict<ReadingListCollectionViewItem>(item);
|
| - if (updater)
|
| - updater(readingListItem.url);
|
| - }
|
| - token.reset();
|
| - self.shouldMonitorModel = YES;
|
| -}
|
| -
|
| -- (void)updateIndexPaths:(NSArray<NSIndexPath*>*)indexPaths
|
| - usingEntryUpdater:(EntryUpdater)updater {
|
| - self.shouldMonitorModel = NO;
|
| - auto token = self.readingListModel->BeginBatchUpdates();
|
| - // Read the objects in reverse order to keep the order (last modified first).
|
| - for (NSIndexPath* index in [indexPaths reverseObjectEnumerator]) {
|
| - CollectionViewItem* cell = [self.collectionViewModel itemAtIndexPath:index];
|
| - ReadingListCollectionViewItem* readingListItem =
|
| - base::mac::ObjCCastStrict<ReadingListCollectionViewItem>(cell);
|
| - if (updater)
|
| - updater(readingListItem.url);
|
| - }
|
| - // Leave the batch update while it is not monitored.
|
| - token.reset();
|
| - self.shouldMonitorModel = YES;
|
| -}
|
| -
|
| -- (void)logDeletionHistogramsForEntry:(const GURL&)url {
|
| - const ReadingListEntry* entry = [self readingListModel]->GetEntryByURL(url);
|
| -
|
| - if (!entry)
|
| - return;
|
| -
|
| - int64_t firstRead = entry->FirstReadTime();
|
| - if (firstRead > 0) {
|
| - // Log 0 if the entry has never been read.
|
| - firstRead = (base::Time::Now() - base::Time::UnixEpoch()).InMicroseconds() -
|
| - firstRead;
|
| - // Convert it to hours.
|
| - firstRead = firstRead / base::Time::kMicrosecondsPerHour;
|
| - }
|
| - UMA_HISTOGRAM_COUNTS_10000("ReadingList.FirstReadAgeOnDeletion", firstRead);
|
| -
|
| - int64_t age = (base::Time::Now() - base::Time::UnixEpoch()).InMicroseconds() -
|
| - entry->CreationTime();
|
| - // Convert it to hours.
|
| - age = age / base::Time::kMicrosecondsPerHour;
|
| - if (entry->IsRead())
|
| - UMA_HISTOGRAM_COUNTS_10000("ReadingList.Read.AgeOnDeletion", age);
|
| - else
|
| - UMA_HISTOGRAM_COUNTS_10000("ReadingList.Unread.AgeOnDeletion", age);
|
| -}
|
| -
|
| -- (void)moveItemsFromSection:(SectionIdentifier)sourceSectionIdentifier
|
| - toSection:(SectionIdentifier)destinationSectionIdentifier {
|
| - [self initializeSection:destinationSectionIdentifier];
|
| -
|
| - NSInteger sourceSection = [self.collectionViewModel
|
| - sectionForSectionIdentifier:sourceSectionIdentifier];
|
| - NSInteger destinationSection = [self.collectionViewModel
|
| - sectionForSectionIdentifier:destinationSectionIdentifier];
|
| - NSInteger numberOfSourceItems =
|
| - [self.collectionViewModel numberOfItemsInSection:sourceSection];
|
| -
|
| - [self.collectionView performBatchUpdates:^{
|
| - for (int index = 0; index < numberOfSourceItems; index++) {
|
| - NSIndexPath* firstItemIndex =
|
| - [NSIndexPath indexPathForItem:0 inSection:sourceSection];
|
| - NSIndexPath* sourceItemIndex =
|
| - [NSIndexPath indexPathForItem:index inSection:sourceSection];
|
| - NSIndexPath* destinationItemIndex =
|
| - [NSIndexPath indexPathForItem:index inSection:destinationSection];
|
| -
|
| - // The collection view model gets updated instantaneously, the collection
|
| - // view does batch updates.
|
| - [self collectionView:self.collectionView
|
| - willMoveItemAtIndexPath:firstItemIndex
|
| - toIndexPath:destinationItemIndex];
|
| - [self.collectionView moveItemAtIndexPath:sourceItemIndex
|
| - toIndexPath:destinationItemIndex];
|
| - }
|
| - }
|
| - completion:^(BOOL) {
|
| - // Reload data to take into account possible sync events.
|
| - [self applyPendingUpdates];
|
| - }];
|
| - // As we modified the section in the batch update block, remove the section in
|
| - // another block.
|
| - [self removeEmptySections];
|
| -}
|
| -
|
| -- (void)moveSelectedItems:(NSArray*)sortedIndexPaths
|
| - toSection:(SectionIdentifier)sectionIdentifier {
|
| - NSInteger sectionCreatedIndex = [self initializeSection:sectionIdentifier];
|
| -
|
| - [self.collectionView performBatchUpdates:^{
|
| - NSInteger section = [self.collectionViewModel
|
| - sectionForSectionIdentifier:sectionIdentifier];
|
| -
|
| - NSInteger newItemIndex = 0;
|
| - for (NSIndexPath* index in sortedIndexPaths) {
|
| - // The |sortedIndexPaths| is a copy of the index paths before the
|
| - // destination section has been added if necessary. The section part of
|
| - // the index potentially needs to be updated.
|
| - NSInteger updatedSection = index.section;
|
| - if (updatedSection >= sectionCreatedIndex)
|
| - updatedSection++;
|
| - if (updatedSection == section) {
|
| - // The item is already in the targeted section, there is no need to move
|
| - // it.
|
| - continue;
|
| - }
|
| -
|
| - NSIndexPath* updatedIndex =
|
| - [NSIndexPath indexPathForItem:index.item inSection:updatedSection];
|
| - NSIndexPath* indexForModel =
|
| - [NSIndexPath indexPathForItem:index.item - newItemIndex
|
| - inSection:updatedSection];
|
| -
|
| - // Index of the item in the new section. The newItemIndex is the index of
|
| - // this item in the targeted section.
|
| - NSIndexPath* newIndexPath =
|
| - [NSIndexPath indexPathForItem:newItemIndex++ inSection:section];
|
| - [self collectionView:self.collectionView
|
| - willMoveItemAtIndexPath:indexForModel
|
| - toIndexPath:newIndexPath];
|
| - [self.collectionView moveItemAtIndexPath:updatedIndex
|
| - toIndexPath:newIndexPath];
|
| - }
|
| - }
|
| - completion:^(BOOL) {
|
| - // Reload data to take into account possible sync events.
|
| - [self applyPendingUpdates];
|
| - }];
|
| - // As we modified the section in the batch update block, remove the section in
|
| - // another block.
|
| - [self removeEmptySections];
|
| -}
|
| -
|
| -- (NSInteger)initializeSection:(SectionIdentifier)sectionIdentifier {
|
| - if (![self.collectionViewModel
|
| - hasSectionForSectionIdentifier:sectionIdentifier]) {
|
| - // The new section IndexPath will be 1 if it is the read section with
|
| - // items in the unread section, 0 otherwise.
|
| - BOOL hasNonEmptySectionAbove =
|
| - sectionIdentifier == SectionIdentifierRead &&
|
| - [self hasItemInSection:SectionIdentifierUnread];
|
| - NSInteger sectionIndex = hasNonEmptySectionAbove ? 1 : 0;
|
| -
|
| - [self.collectionView performBatchUpdates:^{
|
| - [self.collectionViewModel insertSectionWithIdentifier:sectionIdentifier
|
| - atIndex:sectionIndex];
|
| -
|
| - [self.collectionViewModel
|
| - setHeader:[self headerForSection:sectionIdentifier]
|
| - forSectionWithIdentifier:sectionIdentifier];
|
| -
|
| - [self.collectionView
|
| - insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
|
| - }
|
| - completion:nil];
|
| -
|
| - return sectionIndex;
|
| - }
|
| - return NSIntegerMax;
|
| -}
|
| -
|
| -- (CollectionViewTextItem*)headerForSection:
|
| - (SectionIdentifier)sectionIdentifier {
|
| - CollectionViewTextItem* header = nil;
|
| -
|
| - switch (sectionIdentifier) {
|
| - case SectionIdentifierRead:
|
| - header = [[CollectionViewTextItem alloc] initWithType:ItemTypeReadHeader];
|
| - header.text = l10n_util::GetNSString(IDS_IOS_READING_LIST_READ_HEADER);
|
| - break;
|
| -
|
| - case SectionIdentifierUnread:
|
| - header =
|
| - [[CollectionViewTextItem alloc] initWithType:ItemTypeUnreadHeader];
|
| - header.text = l10n_util::GetNSString(IDS_IOS_READING_LIST_UNREAD_HEADER);
|
| - break;
|
| - }
|
| - return header;
|
| -}
|
| -
|
| -- (void)removeEmptySections {
|
| - [self.collectionView performBatchUpdates:^{
|
| -
|
| - SectionIdentifier a[] = {SectionIdentifierRead, SectionIdentifierUnread};
|
| - for (size_t i = 0; i < arraysize(a); i++) {
|
| - SectionIdentifier sectionIdentifier = a[i];
|
| -
|
| - if ([self.collectionViewModel
|
| - hasSectionForSectionIdentifier:sectionIdentifier] &&
|
| - ![self hasItemInSection:sectionIdentifier]) {
|
| - NSInteger section = [self.collectionViewModel
|
| - sectionForSectionIdentifier:sectionIdentifier];
|
| -
|
| - [self.collectionView
|
| - deleteSections:[NSIndexSet indexSetWithIndex:section]];
|
| - [self.collectionViewModel
|
| - removeSectionWithIdentifier:sectionIdentifier];
|
| - }
|
| - }
|
| - }
|
| - completion:nil];
|
| - if (_readingListModel->size() == 0) {
|
| - [self collectionIsEmpty];
|
| - } else {
|
| - [_toolbar setHasReadItem:_readingListModel->unread_size() !=
|
| - _readingListModel->size()];
|
| - }
|
| -}
|
| -
|
| -- (void)exitEditingModeAnimated:(BOOL)animated {
|
| - [self.editor setEditing:NO animated:animated];
|
| - [_toolbar setEditing:NO];
|
| -}
|
| -
|
| -@end
|
|
|