| Index: ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_controller.mm
|
| diff --git a/ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_controller.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..07f5ea19ec6af4daeecf976561edc24c3b34c92b
|
| --- /dev/null
|
| +++ b/ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_controller.mm
|
| @@ -0,0 +1,335 @@
|
| +// 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/tab_switcher/tab_switcher_panel_controller.h"
|
| +
|
| +#include "base/logging.h"
|
| +#import "base/mac/scoped_nsobject.h"
|
| +#include "base/strings/sys_string_conversions.h"
|
| +#import "ios/chrome/browser/tabs/tab.h"
|
| +#include "ios/chrome/browser/ui/ntp/recent_tabs/synced_sessions.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/session_changes.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_cache.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_cell.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_overlay_view.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_view.h"
|
| +
|
| +namespace {
|
| +
|
| +void FillVectorWithHashesUsingDistantSession(
|
| + synced_sessions::DistantSession const& session,
|
| + std::vector<size_t>* hashes) {
|
| + DCHECK(hashes);
|
| + DCHECK(hashes->empty());
|
| + for (size_t i = 0; i < session.tabs.size(); ++i) {
|
| + hashes->push_back(session.tabs[i]->hashOfUserVisibleProperties());
|
| + }
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +@interface TabSwitcherPanelController ()<UICollectionViewDataSource,
|
| + SessionCellDelegate> {
|
| + ios::ChromeBrowserState* _browserState; // Weak.
|
| + base::scoped_nsobject<TabSwitcherPanelView> _panelView;
|
| + base::scoped_nsobject<TabSwitcherModel> _model;
|
| + std::string _sessionTag;
|
| + ios_internal::SessionType _sessionType;
|
| + base::scoped_nsobject<TabSwitcherCache> _cache;
|
| + base::scoped_nsobject<TabSwitcherPanelOverlayView> _overlayView;
|
| + std::unique_ptr<const synced_sessions::DistantSession> _distantSession;
|
| + std::unique_ptr<const TabModelSnapshot> _localSession;
|
| +}
|
| +
|
| +// Changes the visibility of the zero tab state overlay view.
|
| +- (void)setZeroTabStateOverlayVisible:(BOOL)show;
|
| +
|
| +@end
|
| +
|
| +@implementation TabSwitcherPanelController
|
| +
|
| +@synthesize delegate = _delegate;
|
| +@synthesize sessionType = _sessionType;
|
| +
|
| +- (instancetype)initWithModel:(TabSwitcherModel*)model
|
| + forDistantSessionWithTag:(std::string const&)sessionTag
|
| + browserState:(ios::ChromeBrowserState*)browserState {
|
| + self = [super init];
|
| + if (self) {
|
| + DCHECK(model);
|
| + _sessionType = ios_internal::SessionType::DISTANT_SESSION;
|
| + _model.reset([model retain]);
|
| + _distantSession = [model distantSessionForTag:sessionTag];
|
| + _sessionTag = sessionTag;
|
| + _browserState = browserState;
|
| + [self loadView];
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (instancetype)initWithModel:(TabSwitcherModel*)model
|
| + forLocalSessionOfType:(ios_internal::SessionType)sessionType
|
| + withCache:(TabSwitcherCache*)cache
|
| + browserState:(ios::ChromeBrowserState*)browserState {
|
| + self = [super init];
|
| + if (self) {
|
| + DCHECK(model);
|
| + _sessionType = sessionType;
|
| + _model.reset([model retain]);
|
| + _localSession = [model tabModelSnapshotForLocalSession:sessionType];
|
| + _cache.reset([cache retain]);
|
| + _browserState = browserState;
|
| + [self loadView];
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (TabSwitcherPanelView*)view {
|
| + return _panelView;
|
| +}
|
| +
|
| +- (std::string)sessionTag {
|
| + return _sessionTag;
|
| +}
|
| +
|
| +- (void)setDelegate:(id<TabSwitcherPanelControllerDelegate>)delegate {
|
| + _delegate = delegate;
|
| + [[_panelView collectionView] performBatchUpdates:nil completion:nil];
|
| +}
|
| +
|
| +- (BOOL)shouldShowNewTabButton {
|
| + if (_sessionType == ios_internal::SessionType::DISTANT_SESSION) {
|
| + return NO;
|
| + } else {
|
| + return ![self isOverlayVisible];
|
| + }
|
| +}
|
| +
|
| +- (void)updateCollectionViewIfNeeded {
|
| + if (_sessionType == ios_internal::SessionType::DISTANT_SESSION) {
|
| + UICollectionView* collectionView = [_panelView collectionView];
|
| + // TODO(crbug.com/633928) Compute SessionChanges outside of the
|
| + // updateBlock.
|
| + auto updateBlock = ^{
|
| + std::unique_ptr<const synced_sessions::DistantSession> newDistantSession =
|
| + [_model distantSessionForTag:_sessionTag];
|
| + std::vector<size_t> oldTabsHashes;
|
| + std::vector<size_t> newTabsHashes;
|
| + FillVectorWithHashesUsingDistantSession(*_distantSession.get(),
|
| + &oldTabsHashes);
|
| + FillVectorWithHashesUsingDistantSession(*newDistantSession.get(),
|
| + &newTabsHashes);
|
| + ios_internal::SessionChanges changes(oldTabsHashes, newTabsHashes);
|
| + if (changes.hasChanges()) {
|
| + _distantSession = std::move(newDistantSession);
|
| + [self applyChanges:changes toCollectionView:collectionView];
|
| + }
|
| + };
|
| + [collectionView performBatchUpdates:updateBlock completion:nil];
|
| + } else {
|
| + UICollectionView* collectionView = [_panelView collectionView];
|
| + auto updateBlock = ^{
|
| + std::unique_ptr<const TabModelSnapshot> newLocalSession =
|
| + [_model tabModelSnapshotForLocalSession:_sessionType];
|
| + ios_internal::SessionChanges changes(_localSession->hashes(),
|
| + newLocalSession->hashes());
|
| + if (changes.hasChanges()) {
|
| + _localSession = std::move(newLocalSession);
|
| + [self applyChanges:changes toCollectionView:collectionView];
|
| + }
|
| + };
|
| + [collectionView performBatchUpdates:updateBlock completion:nil];
|
| + }
|
| +}
|
| +
|
| +- (void)applyChanges:(ios_internal::SessionChanges&)changes
|
| + toCollectionView:(UICollectionView*)collectionView {
|
| + NSMutableArray* deletedIndexes = [NSMutableArray array];
|
| + NSMutableArray* insertedIndexes = [NSMutableArray array];
|
| + NSMutableArray* updatedIndexes = [NSMutableArray array];
|
| + for (size_t i : changes.deletions()) {
|
| + NSInteger deletedTabIndex = static_cast<NSInteger>(i);
|
| + [deletedIndexes
|
| + addObject:[NSIndexPath indexPathForItem:deletedTabIndex inSection:0]];
|
| + }
|
| + for (size_t i : changes.insertions()) {
|
| + NSInteger insertedTabIndex = static_cast<NSInteger>(i);
|
| + [insertedIndexes
|
| + addObject:[NSIndexPath indexPathForItem:insertedTabIndex inSection:0]];
|
| + }
|
| + for (size_t i : changes.updates()) {
|
| + NSInteger updatedTabIndex = static_cast<NSInteger>(i);
|
| + [updatedIndexes
|
| + addObject:[NSIndexPath indexPathForItem:updatedTabIndex inSection:0]];
|
| + }
|
| + [collectionView deleteItemsAtIndexPaths:deletedIndexes];
|
| + [collectionView insertItemsAtIndexPaths:insertedIndexes];
|
| + [collectionView reloadItemsAtIndexPaths:updatedIndexes];
|
| +}
|
| +
|
| +- (void)scrollTabIndexToVisible:(NSInteger)index triggerLayout:(BOOL)layout {
|
| + [[_panelView collectionView]
|
| + scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]
|
| + atScrollPosition:UICollectionViewScrollPositionTop
|
| + animated:NO];
|
| + if (layout)
|
| + [[_panelView collectionView] layoutIfNeeded];
|
| +}
|
| +
|
| +- (TabSwitcherLocalSessionCell*)localSessionCellForTabAtIndex:(NSInteger)index {
|
| + return (TabSwitcherLocalSessionCell*)[[_panelView collectionView]
|
| + cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
|
| +}
|
| +
|
| +- (CGRect)localSessionCellFrameForTabAtIndex:(NSInteger)index {
|
| + UICollectionViewLayoutAttributes* attributes = [[_panelView collectionView]
|
| + layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:index
|
| + inSection:0]];
|
| + return attributes.frame;
|
| +}
|
| +
|
| +- (void)reload {
|
| + [[_panelView collectionView] reloadSections:[NSIndexSet indexSetWithIndex:0]];
|
| +}
|
| +
|
| +- (UICollectionView*)collectionView {
|
| + return [_panelView collectionView];
|
| +}
|
| +
|
| +#pragma mark - UICollectionViewDataSource
|
| +
|
| +- (NSInteger)collectionView:(UICollectionView*)collectionView
|
| + numberOfItemsInSection:(NSInteger)section {
|
| + DCHECK_EQ(section, 0);
|
| + if (_sessionType == ios_internal::SessionType::DISTANT_SESSION) {
|
| + CHECK(_distantSession);
|
| + return _distantSession->tabs.size();
|
| + } else {
|
| + CHECK(_localSession);
|
| + NSInteger numberOfTabs = _localSession->tabs().size();
|
| + [self setZeroTabStateOverlayVisible:numberOfTabs == 0];
|
| + return numberOfTabs;
|
| + }
|
| +}
|
| +
|
| +- (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
|
| + cellForItemAtIndexPath:(NSIndexPath*)indexPath {
|
| + UICollectionViewCell* cell = nil;
|
| + NSUInteger tabIndex = indexPath.item;
|
| + if (_sessionType == ios_internal::SessionType::DISTANT_SESSION) {
|
| + cell = [collectionView
|
| + dequeueReusableCellWithReuseIdentifier:[TabSwitcherDistantSessionCell
|
| + identifier]
|
| + forIndexPath:indexPath];
|
| + DCHECK([cell isKindOfClass:[TabSwitcherDistantSessionCell class]]);
|
| + TabSwitcherDistantSessionCell* panelCell =
|
| + static_cast<TabSwitcherDistantSessionCell*>(cell);
|
| + CHECK(_distantSession);
|
| + const std::size_t distantSessionTabCount = _distantSession->tabs.size();
|
| + LOG_ASSERT(tabIndex < distantSessionTabCount)
|
| + << "tabIndex == " << tabIndex
|
| + << " _distantSession->tabs.size() == " << distantSessionTabCount;
|
| + synced_sessions::DistantTab* tab = _distantSession->tabs[tabIndex].get();
|
| + CHECK(tab);
|
| + [panelCell setTitle:SysUTF16ToNSString(tab->title)];
|
| + [panelCell setSessionGURL:tab->virtual_url
|
| + withBrowserState:[_model browserState]];
|
| + [panelCell setDelegate:self];
|
| + } else {
|
| + cell = [collectionView
|
| + dequeueReusableCellWithReuseIdentifier:[TabSwitcherLocalSessionCell
|
| + identifier]
|
| + forIndexPath:indexPath];
|
| + DCHECK([cell isKindOfClass:[TabSwitcherLocalSessionCell class]]);
|
| + TabSwitcherLocalSessionCell* panelCell =
|
| + static_cast<TabSwitcherLocalSessionCell*>(cell);
|
| + Tab* tab = _localSession->tabs()[tabIndex];
|
| + [panelCell setDelegate:self];
|
| + [panelCell setSessionType:_sessionType];
|
| + [panelCell setAppearanceForTab:tab cellSize:[_panelView cellSize]];
|
| + }
|
| + return cell;
|
| +}
|
| +
|
| +#pragma mark - SessionCellDelegate
|
| +
|
| +- (TabSwitcherCache*)tabSwitcherCache {
|
| + return _cache;
|
| +}
|
| +
|
| +- (void)cellPressed:(UICollectionViewCell*)cell {
|
| + const NSInteger tabIndex =
|
| + [[_panelView collectionView] indexPathForCell:cell].item;
|
| +
|
| + if (_sessionType == ios_internal::SessionType::DISTANT_SESSION) {
|
| + synced_sessions::DistantTab* tab = _distantSession->tabs[tabIndex].get();
|
| + if (tab)
|
| + [self.delegate tabSwitcherPanelController:self didSelectDistantTab:tab];
|
| + } else {
|
| + Tab* tab = _localSession->tabs()[tabIndex];
|
| + if (tab)
|
| + [self.delegate tabSwitcherPanelController:self didSelectLocalTab:tab];
|
| + }
|
| +}
|
| +
|
| +- (void)deleteButtonPressedForCell:(UICollectionViewCell*)cell {
|
| + DCHECK(_sessionType != ios_internal::SessionType::DISTANT_SESSION);
|
| + const NSInteger tabIndex =
|
| + [[_panelView collectionView] indexPathForCell:cell].item;
|
| + Tab* tab = _localSession->tabs()[tabIndex];
|
| + if (tab)
|
| + [self.delegate tabSwitcherPanelController:self didCloseLocalTab:tab];
|
| +}
|
| +
|
| +#pragma mark - Private
|
| +
|
| +- (BOOL)isOverlayVisible {
|
| + return _overlayView && [_overlayView alpha] != 0.0;
|
| +}
|
| +
|
| +- (void)setZeroTabStateOverlayVisible:(BOOL)show {
|
| + if (show == [self isOverlayVisible])
|
| + return;
|
| +
|
| + DCHECK(ios_internal::IsLocalSession(_sessionType));
|
| +
|
| + if (!_overlayView) {
|
| + _overlayView.reset([[TabSwitcherPanelOverlayView alloc]
|
| + initWithFrame:[_panelView bounds]
|
| + browserState:_browserState]);
|
| + [_overlayView
|
| + setOverlayType:
|
| + (_sessionType == ios_internal::SessionType::OFF_THE_RECORD_SESSION)
|
| + ? TabSwitcherPanelOverlayType::
|
| + OVERLAY_PANEL_USER_NO_INCOGNITO_TABS
|
| + : TabSwitcherPanelOverlayType::OVERLAY_PANEL_USER_NO_OPEN_TABS];
|
| +
|
| + [_overlayView setAlpha:0];
|
| + [_overlayView setAutoresizingMask:UIViewAutoresizingFlexibleHeight |
|
| + UIViewAutoresizingFlexibleWidth];
|
| + [_panelView addSubview:_overlayView];
|
| + [_overlayView setNeedsLayout];
|
| + }
|
| +
|
| + [UIView
|
| + animateWithDuration:0.25
|
| + animations:^{
|
| + [_overlayView setAlpha:show ? 1.0 : 0.0];
|
| + [self.delegate
|
| + tabSwitcherPanelControllerDidUpdateOverlayViewVisibility:
|
| + self];
|
| + }];
|
| +}
|
| +
|
| +- (void)loadView {
|
| + _panelView.reset(
|
| + [[TabSwitcherPanelView alloc] initWithSessionType:_sessionType]);
|
| + _panelView.get().collectionView.dataSource = self;
|
| +}
|
| +
|
| +- (synced_sessions::DistantSession const*)distantSession {
|
| + return _distantSession.get();
|
| +}
|
| +
|
| +@end
|
|
|