Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(100)

Unified Diff: ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm

Issue 2589803002: Upstream Chrome on iOS source code [6/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm
diff --git a/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm b/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm
new file mode 100644
index 0000000000000000000000000000000000000000..10a9b92e35568b124494ff681ee06e5747bb5512
--- /dev/null
+++ b/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm
@@ -0,0 +1,952 @@
+// Copyright 2014 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/ntp/recent_tabs/recent_tabs_table_view_controller.h"
+
+#include <memory>
+
+#import "base/ios/weak_nsobject.h"
+#include "base/logging.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/metrics/user_metrics.h"
+#include "base/metrics/user_metrics_action.h"
+#include "base/strings/sys_string_conversions.h"
+#include "components/browser_sync/profile_sync_service.h"
+#include "components/sync/driver/sync_service.h"
+#include "components/sync_sessions/open_tabs_ui_delegate.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/metrics/new_tab_page_uma.h"
+#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios.h"
+#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios_factory.h"
+#include "ios/chrome/browser/sync/ios_chrome_profile_sync_service_factory.h"
+#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
+#import "ios/chrome/browser/ui/commands/generic_chrome_command.h"
+#include "ios/chrome/browser/ui/commands/ios_command_ids.h"
+#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
+#include "ios/chrome/browser/ui/ntp/recent_tabs/synced_sessions.h"
+#import "ios/chrome/browser/ui/ntp/recent_tabs/views/generic_section_header_view.h"
+#import "ios/chrome/browser/ui/ntp/recent_tabs/views/header_of_collapsable_section_protocol.h"
+#import "ios/chrome/browser/ui/ntp/recent_tabs/views/session_section_header_view.h"
+#import "ios/chrome/browser/ui/ntp/recent_tabs/views/session_tab_data_view.h"
+#import "ios/chrome/browser/ui/ntp/recent_tabs/views/show_full_history_view.h"
+#import "ios/chrome/browser/ui/ntp/recent_tabs/views/signed_in_sync_in_progress_view.h"
+#import "ios/chrome/browser/ui/ntp/recent_tabs/views/signed_in_sync_off_view.h"
+#import "ios/chrome/browser/ui/ntp/recent_tabs/views/signed_in_sync_on_no_sessions_view.h"
+#import "ios/chrome/browser/ui/ntp/recent_tabs/views/signed_out_view.h"
+#import "ios/chrome/browser/ui/ntp/recent_tabs/views/spacers_view.h"
+#include "ios/chrome/browser/ui/ui_util.h"
+#import "ios/chrome/browser/ui/uikit_ui_util.h"
+#import "ios/chrome/browser/ui/url_loader.h"
+#include "ios/chrome/grit/ios_strings.h"
+#include "ios/web/public/referrer.h"
+#import "ios/web/public/web_state/context_menu_params.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+
+// Key for saving collapsed session state in the UserDefaults.
+NSString* const kCollapsedSectionsKey = @"ChromeRecentTabsCollapsedSections";
+
+// Key for saving whether the Other Device section is collapsed.
+NSString* const kOtherDeviceCollapsedKey = @"OtherDevicesCollapsed";
+
+// Key for saving whether the Recently Closed section is collapsed.
+NSString* const kRecentlyClosedCollapsedKey = @"RecentlyClosedCollapsed";
+
+// Tag to extract the section headers from the cells.
+enum { kSectionHeader = 1 };
+
+// Types of sections.
+enum SectionType {
+ SEPARATOR_SECTION,
+ CLOSED_TAB_SECTION,
+ OTHER_DEVICES_SECTION,
+ SESSION_SECTION,
+};
+
+// Types of cells.
+enum CellType {
+ CELL_CLOSED_TAB_SECTION_HEADER,
+ CELL_CLOSED_TAB_DATA,
+ CELL_SHOW_FULL_HISTORY,
+ CELL_SEPARATOR,
+ CELL_OTHER_DEVICES_SECTION_HEADER,
+ CELL_OTHER_DEVICES_SIGNED_OUT,
+ CELL_OTHER_DEVICES_SIGNED_IN_SYNC_OFF,
+ CELL_OTHER_DEVICES_SIGNED_IN_SYNC_ON_NO_SESSIONS,
+ CELL_OTHER_DEVICES_SYNC_IN_PROGRESS,
+ CELL_SESSION_SECTION_HEADER,
+ CELL_SESSION_TAB_DATA,
+};
+
+} // namespace
+
+@interface RecentTabsTableViewController () {
+ ios::ChromeBrowserState* _browserState; // weak
+ // The service that manages the recently closed tabs.
+ sessions::TabRestoreService* _tabRestoreService; // weak
+ // Loader used to open new tabs.
+ id<UrlLoader> _loader; // weak
+ // The sync state.
+ SessionsSyncUserState _sessionState;
+ // The synced sessions.
+ std::unique_ptr<synced_sessions::SyncedSessions> _syncedSessions;
+ // Handles displaying the context menu for all form factors.
+ base::scoped_nsobject<ContextMenuCoordinator> _contextMenuCoordinator;
+}
+// Returns the type of the section at index |section|.
+- (SectionType)sectionType:(NSInteger)section;
+// Returns the type of the cell at the path |indexPath|.
+- (CellType)cellType:(NSIndexPath*)indexPath;
+// Returns the number of sections before the other devices or session sections.
+- (NSInteger)numberOfSectionsBeforeSessionOrOtherDevicesSections;
+// Dismisses the modal containing the Recent Tabs panel (iPhone only).
+- (void)dismissRecentTabsModal;
+// Dismisses the modal containing the Recent Tabs panel, with completion
+// handler (iPhone only).
+- (void)dismissRecentTabsModalWithCompletion:(ProceduralBlock)completion;
+// Opens a new tab with the content of |distantTab|.
+- (void)openTabWithContentOfDistantTab:
+ (synced_sessions::DistantTab const*)distantTab;
+// Opens a new tab with |url|.
+- (void)openTabWithURL:(const GURL&)url;
+// Shows the user's full history.
+- (void)showFullHistory;
+// Deletes/inserts cells for section at index |sectionIndex|.
+- (void)toggleExpansionOfSection:(NSInteger)sectionIndex;
+// Returns the key used to map |distantSession| to a collapsed status.
+- (NSString*)keyForDistantSession:
+ (synced_sessions::DistantSession const*)distantSession;
+// Sets whether the session addressed with |sectionKey| is collapsed.
+- (void)setSection:(NSString*)sectionKey collapsed:(BOOL)collapsed;
+// Returns whether the section addressed with |sectionKey| is collapsed.
+- (BOOL)sectionIsCollapsed:(NSString*)sectionKey;
+// Returns the number of session sections. Requires |_sessionState| to be
+// USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS.
+- (NSInteger)numberOfSessionSections;
+// Returns the section indexes of the Session section or the Other Devices
+// section.
+- (NSIndexSet*)sessionOrOtherDevicesSectionsIndexes;
+// Returns the index of the session located at |indexPath|.
+- (size_t)indexOfSessionAtIndexPath:(NSIndexPath*)indexPath;
+// Returns the session at |indexPath|.
+- (synced_sessions::DistantSession const*)sessionAtIndexPath:
+ (NSIndexPath*)indexPath;
+// Returns the session tab at the index |indexPath|.
+- (synced_sessions::DistantTab const*)distantTabAtIndex:(NSIndexPath*)indexPath;
+// Opens in new tabs all the tabs of the distant session at index |indexPath|.
+- (void)openTabsFromSessionAtIndexPath:(NSIndexPath*)indexPath;
+// Removes all the cells of the session section at index |indexPath|.
+- (void)removeSessionAtIndexPath:(NSIndexPath*)indexPath;
+// Handles long presses on the UITableView, possibly opening context menus.
+- (void)handleLongPress:(UILongPressGestureRecognizer*)longPressGesture;
+@end
+
+@implementation RecentTabsTableViewController
+
+@synthesize delegate = delegate_;
+
+- (instancetype)init {
+ NOTREACHED();
+ return nil;
+}
+
+- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
+ loader:(id<UrlLoader>)loader {
+ self = [super initWithStyle:UITableViewStylePlain];
+ if (self) {
+ DCHECK(browserState);
+ DCHECK(loader);
+ _browserState = browserState;
+ _loader = loader;
+ _sessionState = SessionsSyncUserState::USER_SIGNED_OUT;
+ _syncedSessions.reset(new synced_sessions::SyncedSessions());
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [self.tableView removeObserver:self forKeyPath:@"contentSize"];
+ [super dealloc];
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ self.view.accessibilityIdentifier = @"recent_tabs_view_controller";
+ [self.tableView setSeparatorColor:[UIColor clearColor]];
+ [self.tableView setDataSource:self];
+ [self.tableView setDelegate:self];
+ base::scoped_nsobject<UILongPressGestureRecognizer> longPress(
+ [[UILongPressGestureRecognizer alloc]
+ initWithTarget:self
+ action:@selector(handleLongPress:)]);
+ longPress.get().delegate = self;
+ [self.tableView addGestureRecognizer:longPress];
+
+ [self.tableView addObserver:self
+ forKeyPath:@"contentSize"
+ options:0
+ context:NULL];
+}
+
+- (void)observeValueForKeyPath:(NSString*)keyPath
+ ofObject:(id)object
+ change:(NSDictionary*)change
+ context:(void*)context {
+ if ([keyPath isEqualToString:@"contentSize"])
+ [delegate_ recentTabsTableViewContentMoved:self.tableView];
+}
+
+- (SectionType)sectionType:(NSInteger)section {
+ if (section == 0) {
+ return CLOSED_TAB_SECTION;
+ }
+ if (section == 1) {
+ return SEPARATOR_SECTION;
+ }
+ if (section < [self numberOfSectionsBeforeSessionOrOtherDevicesSections]) {
+ return CLOSED_TAB_SECTION;
+ }
+ if (_sessionState ==
+ SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS) {
+ return SESSION_SECTION;
+ }
+ // Other cases of recent_tabs::USER_SIGNED_IN_SYNC_OFF,
+ // recent_tabs::USER_SIGNED_IN_SYNC_ON_NO_SESSIONS, and
+ // recent_tabs::USER_SIGNED_OUT falls through to here.
+ return OTHER_DEVICES_SECTION;
+}
+
+- (CellType)cellType:(NSIndexPath*)indexPath {
+ SectionType sectionType = [self sectionType:indexPath.section];
+ switch (sectionType) {
+ case CLOSED_TAB_SECTION:
+ if (indexPath.row == 0) {
+ return CELL_CLOSED_TAB_SECTION_HEADER;
+ }
+ // The last cell of the section is to access the history panel.
+ if (indexPath.row ==
+ [self numberOfCellsInRecentlyClosedTabsSection] - 1) {
+ return CELL_SHOW_FULL_HISTORY;
+ }
+ return CELL_CLOSED_TAB_DATA;
+ case SEPARATOR_SECTION:
+ return CELL_SEPARATOR;
+ case SESSION_SECTION:
+ if (indexPath.row == 0) {
+ return CELL_SESSION_SECTION_HEADER;
+ }
+ return CELL_SESSION_TAB_DATA;
+ case OTHER_DEVICES_SECTION:
+ if (_sessionState ==
+ SessionsSyncUserState::USER_SIGNED_IN_SYNC_IN_PROGRESS) {
+ return CELL_OTHER_DEVICES_SYNC_IN_PROGRESS;
+ }
+ if (indexPath.row == 0) {
+ return CELL_OTHER_DEVICES_SECTION_HEADER;
+ }
+ switch (_sessionState) {
+ case SessionsSyncUserState::USER_SIGNED_OUT:
+ return CELL_OTHER_DEVICES_SIGNED_OUT;
+ case SessionsSyncUserState::USER_SIGNED_IN_SYNC_OFF:
+ return CELL_OTHER_DEVICES_SIGNED_IN_SYNC_OFF;
+ case SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_NO_SESSIONS:
+ return CELL_OTHER_DEVICES_SIGNED_IN_SYNC_ON_NO_SESSIONS;
+ case SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS:
+ case SessionsSyncUserState::USER_SIGNED_IN_SYNC_IN_PROGRESS:
+ NOTREACHED();
+ // These cases should never occur. Still, this method needs to
+ // return _something_, so it's returning the least wrong cell type.
+ return CELL_OTHER_DEVICES_SIGNED_IN_SYNC_ON_NO_SESSIONS;
+ }
+ }
+}
+
+- (NSInteger)numberOfSectionsBeforeSessionOrOtherDevicesSections {
+ // The 2 sections are CLOSED_TAB_SECTION and SEPARATOR_SECTION.
+ return 2;
+}
+
+- (void)setScrollsToTop:(BOOL)enabled {
+ [self.tableView setScrollsToTop:enabled];
+}
+
+- (void)dismissModals {
+ [_contextMenuCoordinator stop];
+}
+
+#pragma mark - Recently closed tab helpers
+
+- (void)refreshRecentlyClosedTabs {
+ [self.tableView reloadData];
+}
+
+- (void)setTabRestoreService:(sessions::TabRestoreService*)tabRestoreService {
+ _tabRestoreService = tabRestoreService;
+}
+
+- (NSInteger)numberOfCellsInRecentlyClosedTabsSection {
+ // + 2 because of the section header, and the "Show full history" cell.
+ return [self numberOfRecentlyClosedTabs] + 2;
+}
+
+- (NSInteger)numberOfRecentlyClosedTabs {
+ if (!_tabRestoreService)
+ return 0;
+ return static_cast<NSInteger>(_tabRestoreService->entries().size());
+}
+
+- (const sessions::TabRestoreService::Entry*)tabRestoreEntryAtIndex:
+ (NSIndexPath*)indexPath {
+ DCHECK_EQ([self sectionType:indexPath.section], CLOSED_TAB_SECTION);
+ // "- 1" because of the section header.
+ NSInteger index = indexPath.row - 1;
+ DCHECK_LE(index, [self numberOfRecentlyClosedTabs]);
+ if (!_tabRestoreService)
+ return nullptr;
+
+ // Advance the entry iterator to the correct index.
+ // Note that std:list<> can only be accessed sequentially, which is
+ // suboptimal when using Cocoa table APIs. This list doesn't appear
+ // to get very long, so it probably won't matter for perf.
+ sessions::TabRestoreService::Entries::const_iterator iter =
+ _tabRestoreService->entries().begin();
+ std::advance(iter, index);
+ CHECK(*iter);
+ return iter->get();
+}
+
+#pragma mark - Helpers to open tabs, or show the full history view.
+
+- (void)dismissRecentTabsModal {
+ [self dismissRecentTabsModalWithCompletion:nil];
+}
+
+- (void)dismissRecentTabsModalWithCompletion:(ProceduralBlock)completion {
+ // Recent Tabs are modally presented only on iPhone.
+ if (!IsIPadIdiom()) {
+ // TODO(crbug.com/434683): Use a delegate to dismiss the table view.
+ [self.tableView.window.rootViewController
+ dismissViewControllerAnimated:YES
+ completion:completion];
+ }
+}
+
+- (void)openTabWithContentOfDistantTab:
+ (synced_sessions::DistantTab const*)distantTab {
+ sync_sessions::OpenTabsUIDelegate* openTabs =
+ IOSChromeProfileSyncServiceFactory::GetForBrowserState(_browserState)
+ ->GetOpenTabsUIDelegate();
+ const sessions::SessionTab* toLoad = nullptr;
+ [self dismissRecentTabsModal];
+ if (openTabs->GetForeignTab(distantTab->session_tag, distantTab->tab_id,
+ &toLoad)) {
+ base::RecordAction(base::UserMetricsAction("MobileNTPForeignSession"));
+ new_tab_page_uma::RecordAction(
+ _browserState, new_tab_page_uma::ACTION_OPENED_FOREIGN_SESSION);
+ [_loader loadSessionTab:toLoad];
+ }
+}
+
+- (void)openTabWithTabRestoreEntry:
+ (const sessions::TabRestoreService::Entry*)entry {
+ DCHECK(entry);
+ if (!entry)
+ return;
+ // We only handle the TAB type.
+ if (entry->type != sessions::TabRestoreService::TAB)
+ return;
+ TabRestoreServiceDelegateImplIOS* delegate =
+ TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
+ _browserState);
+ [self dismissRecentTabsModal];
+ base::RecordAction(base::UserMetricsAction("MobileNTPRecentlyClosed"));
+ new_tab_page_uma::RecordAction(
+ _browserState, new_tab_page_uma::ACTION_OPENED_RECENTLY_CLOSED_ENTRY);
+ _tabRestoreService->RestoreEntryById(delegate, entry->id,
+ WindowOpenDisposition::CURRENT_TAB);
+}
+
+- (void)openTabWithURL:(const GURL&)url {
+ if (url.is_valid()) {
+ [self dismissRecentTabsModal];
+ [_loader loadURL:url
+ referrer:web::Referrer()
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ }
+}
+
+- (void)showFullHistory {
+ UIViewController* rootViewController =
+ self.tableView.window.rootViewController;
+ ProceduralBlock openHistory = ^{
+ base::scoped_nsobject<GenericChromeCommand> openHistory(
+ [[GenericChromeCommand alloc] initWithTag:IDC_SHOW_HISTORY]);
+ [rootViewController chromeExecuteCommand:openHistory];
+ };
+ // Dismiss modal, if shown, and open history.
+ if (IsIPadIdiom()) {
+ openHistory();
+ } else {
+ [self dismissRecentTabsModalWithCompletion:openHistory];
+ }
+}
+
+#pragma mark - Handling of the collapsed sections.
+
+- (void)toggleExpansionOfSection:(NSInteger)sectionIndex {
+ NSString* sectionCollapseKey = nil;
+ int cellCount = 0;
+
+ SectionType section = [self sectionType:sectionIndex];
+
+ switch (section) {
+ case CLOSED_TAB_SECTION:
+ sectionCollapseKey = kRecentlyClosedCollapsedKey;
+ // - 1 because the header does not count.
+ cellCount = [self numberOfCellsInRecentlyClosedTabsSection] - 1;
+ break;
+ case SEPARATOR_SECTION:
+ NOTREACHED();
+ return;
+ case OTHER_DEVICES_SECTION:
+ cellCount = 1;
+ sectionCollapseKey = kOtherDeviceCollapsedKey;
+ break;
+ case SESSION_SECTION: {
+ size_t indexOfSession =
+ sectionIndex -
+ [self numberOfSectionsBeforeSessionOrOtherDevicesSections];
+ DCHECK_LT(indexOfSession, _syncedSessions->GetSessionCount());
+ synced_sessions::DistantSession const* distantSession =
+ _syncedSessions->GetSession(indexOfSession);
+ cellCount = distantSession->tabs.size();
+ sectionCollapseKey = [self keyForDistantSession:distantSession];
+ break;
+ }
+ }
+ DCHECK(sectionCollapseKey);
+ BOOL collapsed = ![self sectionIsCollapsed:sectionCollapseKey];
+ [self setSection:sectionCollapseKey collapsed:collapsed];
+
+ // Builds an array indexing all the cells needing to be removed or inserted to
+ // collapse/expand the section.
+ NSMutableArray* cellIndexPathsToDeleteOrInsert = [NSMutableArray array];
+ for (int i = 1; i <= cellCount; i++) {
+ NSIndexPath* tabIndexPath =
+ [NSIndexPath indexPathForRow:i inSection:sectionIndex];
+ [cellIndexPathsToDeleteOrInsert addObject:tabIndexPath];
+ }
+
+ // Update the table view.
+ [self.tableView beginUpdates];
+ if (collapsed) {
+ [self.tableView deleteRowsAtIndexPaths:cellIndexPathsToDeleteOrInsert
+ withRowAnimation:UITableViewRowAnimationFade];
+ } else {
+ [self.tableView insertRowsAtIndexPaths:cellIndexPathsToDeleteOrInsert
+ withRowAnimation:UITableViewRowAnimationFade];
+ }
+ [self.tableView endUpdates];
+
+ // Rotate disclosure icon.
+ NSIndexPath* sectionCellIndexPath =
+ [NSIndexPath indexPathForRow:0 inSection:sectionIndex];
+ UITableViewCell* sectionCell =
+ [self.tableView cellForRowAtIndexPath:sectionCellIndexPath];
+ UIView* subview = [sectionCell viewWithTag:kSectionHeader];
+ DCHECK([subview
+ conformsToProtocol:@protocol(HeaderOfCollapsableSectionProtocol)]);
+ id<HeaderOfCollapsableSectionProtocol> headerView =
+ static_cast<id<HeaderOfCollapsableSectionProtocol>>(subview);
+ [headerView setSectionIsCollapsed:collapsed animated:YES];
+}
+
+- (NSString*)keyForDistantSession:
+ (synced_sessions::DistantSession const*)distantSession {
+ return base::SysUTF8ToNSString(distantSession->tag);
+}
+
+- (void)setSection:(NSString*)sectionKey collapsed:(BOOL)collapsed {
+ // TODO(jif): Store in the browser state preference instead of NSUserDefaults.
+ // crbug.com/419346.
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+ NSDictionary* collapsedSections =
+ [defaults dictionaryForKey:kCollapsedSectionsKey];
+ NSMutableDictionary* newCollapsedSessions =
+ [NSMutableDictionary dictionaryWithDictionary:collapsedSections];
+ NSNumber* value = [NSNumber numberWithBool:collapsed];
+ [newCollapsedSessions setValue:value forKey:sectionKey];
+ [defaults setObject:newCollapsedSessions forKey:kCollapsedSectionsKey];
+}
+
+- (BOOL)sectionIsCollapsed:(NSString*)sectionKey {
+ // TODO(crbug.com/419346): Store in the profile's preference instead of the
+ // NSUserDefaults.
+ DCHECK(sectionKey);
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+ NSDictionary* collapsedSessions =
+ [defaults dictionaryForKey:kCollapsedSectionsKey];
+ NSNumber* value = (NSNumber*)[collapsedSessions valueForKey:sectionKey];
+ return [value boolValue];
+}
+
+#pragma mark - Distant Sessions helpers
+
+- (void)refreshUserState:(SessionsSyncUserState)newSessionState {
+ if (newSessionState == _sessionState &&
+ _sessionState !=
+ SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS) {
+ // No need to refresh the sections.
+ return;
+ }
+
+ [self.tableView beginUpdates];
+ NSIndexSet* indexesToBeDeleted = [self sessionOrOtherDevicesSectionsIndexes];
+ [self.tableView deleteSections:indexesToBeDeleted
+ withRowAnimation:UITableViewRowAnimationFade];
+ syncer::SyncService* syncService =
+ IOSChromeProfileSyncServiceFactory::GetForBrowserState(_browserState);
+ _syncedSessions.reset(new synced_sessions::SyncedSessions(syncService));
+ _sessionState = newSessionState;
+
+ if (_sessionState == SessionsSyncUserState::USER_SIGNED_IN_SYNC_IN_PROGRESS) {
+ // Expand the "Other Device" section once sync is finished.
+ [self setSection:kOtherDeviceCollapsedKey collapsed:NO];
+ }
+
+ NSIndexSet* indexesToBeInserted = [self sessionOrOtherDevicesSectionsIndexes];
+ [self.tableView insertSections:indexesToBeInserted
+ withRowAnimation:UITableViewRowAnimationFade];
+ [self.tableView endUpdates];
+}
+
+- (NSInteger)numberOfSessionSections {
+ DCHECK(_sessionState ==
+ SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS);
+ return _syncedSessions->GetSessionCount();
+}
+
+- (NSIndexSet*)sessionOrOtherDevicesSectionsIndexes {
+ NSInteger sectionCount = 0;
+ switch (_sessionState) {
+ case SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS:
+ sectionCount = [self numberOfSessionSections];
+ break;
+ case SessionsSyncUserState::USER_SIGNED_IN_SYNC_OFF:
+ case SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_NO_SESSIONS:
+ case SessionsSyncUserState::USER_SIGNED_OUT:
+ case SessionsSyncUserState::USER_SIGNED_IN_SYNC_IN_PROGRESS:
+ sectionCount = 1;
+ break;
+ }
+ NSRange rangeOfSessionSections = NSMakeRange(
+ [self numberOfSectionsBeforeSessionOrOtherDevicesSections], sectionCount);
+ NSIndexSet* sessionSectionsIndexes =
+ [NSIndexSet indexSetWithIndexesInRange:rangeOfSessionSections];
+ return sessionSectionsIndexes;
+}
+
+- (size_t)indexOfSessionAtIndexPath:(NSIndexPath*)indexPath {
+ DCHECK_EQ([self sectionType:indexPath.section], SESSION_SECTION);
+ size_t indexOfSession =
+ indexPath.section -
+ [self numberOfSectionsBeforeSessionOrOtherDevicesSections];
+ DCHECK_LT(indexOfSession, _syncedSessions->GetSessionCount());
+ return indexOfSession;
+}
+
+- (synced_sessions::DistantSession const*)sessionAtIndexPath:
+ (NSIndexPath*)indexPath {
+ return _syncedSessions->GetSession(
+ [self indexOfSessionAtIndexPath:indexPath]);
+}
+
+- (synced_sessions::DistantTab const*)distantTabAtIndex:
+ (NSIndexPath*)indexPath {
+ DCHECK_EQ([self sectionType:indexPath.section], SESSION_SECTION);
+ // "- 1" because of the section header.
+ size_t indexOfDistantTab = indexPath.row - 1;
+ synced_sessions::DistantSession const* session =
+ [self sessionAtIndexPath:indexPath];
+ DCHECK_LT(indexOfDistantTab, session->tabs.size());
+ return session->tabs[indexOfDistantTab].get();
+}
+
+#pragma mark - Long press and context menus
+
+- (void)handleLongPress:(UILongPressGestureRecognizer*)longPressGesture {
+ DCHECK_EQ(self.tableView, longPressGesture.view);
+ if (longPressGesture.state == UIGestureRecognizerStateBegan) {
+ CGPoint point = [longPressGesture locationInView:self.tableView];
+ NSIndexPath* indexPath = [self.tableView indexPathForRowAtPoint:point];
+ if (!indexPath)
+ return;
+ DCHECK_LE(indexPath.section,
+ [self numberOfSectionsInTableView:self.tableView]);
+
+ CellType cellType = [self cellType:indexPath];
+ if (cellType != CELL_SESSION_SECTION_HEADER) {
+ NOTREACHED();
+ return;
+ }
+
+ web::ContextMenuParams params;
+ // Get view coordinates in local space.
+ CGPoint viewCoordinate = [longPressGesture locationInView:self.tableView];
+ params.location = viewCoordinate;
+ params.view.reset([self.tableView retain]);
+
+ // Present sheet/popover using controller that is added to view hierarchy.
+ UIViewController* topController = [params.view window].rootViewController;
+ while (topController.presentedViewController)
+ topController = topController.presentedViewController;
+
+ _contextMenuCoordinator.reset([[ContextMenuCoordinator alloc]
+ initWithBaseViewController:topController
+ params:params]);
+
+ // Fill the sheet/popover with buttons.
+ base::WeakNSObject<RecentTabsTableViewController> weakSelf(self);
+
+ // "Open all tabs" button.
+ NSString* openAllButtonLabel =
+ l10n_util::GetNSString(IDS_IOS_RECENT_TABS_OPEN_ALL_MENU_OPTION);
+ [_contextMenuCoordinator
+ addItemWithTitle:openAllButtonLabel
+ action:^{
+ [weakSelf openTabsFromSessionAtIndexPath:indexPath];
+ }];
+
+ // "Hide for now" button.
+ NSString* hideButtonLabel =
+ l10n_util::GetNSString(IDS_IOS_RECENT_TABS_HIDE_MENU_OPTION);
+ [_contextMenuCoordinator
+ addItemWithTitle:hideButtonLabel
+ action:^{
+ [weakSelf removeSessionAtIndexPath:indexPath];
+ }];
+
+ [_contextMenuCoordinator start];
+ }
+}
+
+- (void)openTabsFromSessionAtIndexPath:(NSIndexPath*)indexPath {
+ synced_sessions::DistantSession const* session =
+ [self sessionAtIndexPath:indexPath];
+ [self dismissRecentTabsModal];
+ for (auto const& tab : session->tabs) {
+ [_loader webPageOrderedOpen:tab->virtual_url
+ referrer:web::Referrer()
+ windowName:nil
+ inBackground:YES
+ appendTo:kLastTab];
+ }
+}
+
+- (void)removeSessionAtIndexPath:(NSIndexPath*)indexPath {
+ DCHECK_EQ([self cellType:indexPath], CELL_SESSION_SECTION_HEADER);
+ synced_sessions::DistantSession const* session =
+ [self sessionAtIndexPath:indexPath];
+ std::string sessionTagCopy = session->tag;
+ syncer::SyncService* syncService =
+ IOSChromeProfileSyncServiceFactory::GetForBrowserState(_browserState);
+ sync_sessions::OpenTabsUIDelegate* openTabs =
+ syncService->GetOpenTabsUIDelegate();
+ _syncedSessions->EraseSession([self indexOfSessionAtIndexPath:indexPath]);
+ [self.tableView
+ deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section]
+ withRowAnimation:UITableViewRowAnimationLeft];
+ // Use dispatch_async to give the action sheet a chance to cleanup before
+ // replacing its parent view.
+ dispatch_async(dispatch_get_main_queue(), ^{
+ openTabs->DeleteForeignSession(sessionTagCopy);
+ });
+}
+
+#pragma mark - UIGestureRecognizerDelegate
+
+- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
+ CGPoint point = [gestureRecognizer locationInView:self.tableView];
+ NSIndexPath* indexPath = [self.tableView indexPathForRowAtPoint:point];
+ if (!indexPath)
+ return NO;
+ CellType cellType = [self cellType:indexPath];
+ // Context menus can be opened on a section header for tabs.
+ return cellType == CELL_SESSION_SECTION_HEADER;
+}
+
+#pragma mark - UITableViewDataSource
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView {
+ switch (_sessionState) {
+ case SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS:
+ return [self numberOfSectionsBeforeSessionOrOtherDevicesSections] +
+ [self numberOfSessionSections];
+ case SessionsSyncUserState::USER_SIGNED_IN_SYNC_OFF:
+ case SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_NO_SESSIONS:
+ case SessionsSyncUserState::USER_SIGNED_OUT:
+ case SessionsSyncUserState::USER_SIGNED_IN_SYNC_IN_PROGRESS:
+ return [self numberOfSectionsBeforeSessionOrOtherDevicesSections] + 1;
+ }
+}
+
+- (NSInteger)tableView:(UITableView*)tableView
+ numberOfRowsInSection:(NSInteger)section {
+ switch ([self sectionType:section]) {
+ case CLOSED_TAB_SECTION:
+ if ([self sectionIsCollapsed:kRecentlyClosedCollapsedKey])
+ return 1;
+ else
+ return [self numberOfCellsInRecentlyClosedTabsSection];
+ case SEPARATOR_SECTION:
+ return 1;
+ case OTHER_DEVICES_SECTION:
+ if (_sessionState ==
+ SessionsSyncUserState::USER_SIGNED_IN_SYNC_IN_PROGRESS)
+ return 1;
+ if ([self sectionIsCollapsed:kOtherDeviceCollapsedKey])
+ return 1;
+ else
+ return 2;
+ case SESSION_SECTION: {
+ DCHECK(_sessionState ==
+ SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS);
+ size_t sessionIndex =
+ section - [self numberOfSectionsBeforeSessionOrOtherDevicesSections];
+ DCHECK_LT(sessionIndex, _syncedSessions->GetSessionCount());
+ synced_sessions::DistantSession const* distantSession =
+ _syncedSessions->GetSession(sessionIndex);
+ NSString* key = [self keyForDistantSession:distantSession];
+ if ([self sectionIsCollapsed:key])
+ return 1;
+ else
+ return distantSession->tabs.size() + 1;
+ }
+ }
+}
+
+- (UITableViewCell*)tableView:(UITableView*)tableView
+ cellForRowAtIndexPath:(NSIndexPath*)indexPath {
+ UITableViewCell* cell =
+ [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
+ reuseIdentifier:nil] autorelease];
+ UIView* contentView = cell.contentView;
+
+ base::scoped_nsobject<UIView> subview;
+ CellType cellType = [self cellType:indexPath];
+ switch (cellType) {
+ case CELL_CLOSED_TAB_SECTION_HEADER: {
+ BOOL collapsed = [self sectionIsCollapsed:kRecentlyClosedCollapsedKey];
+ subview.reset([[GenericSectionHeaderView alloc]
+ initWithType:recent_tabs::RECENTLY_CLOSED_TABS_SECTION_HEADER
+ sectionIsCollapsed:collapsed]);
+ [subview setTag:kSectionHeader];
+ break;
+ }
+ case CELL_CLOSED_TAB_DATA: {
+ base::scoped_nsobject<SessionTabDataView> genericTabData(
+ [[SessionTabDataView alloc] initWithFrame:CGRectZero]);
+ [genericTabData
+ updateWithTabRestoreEntry:[self tabRestoreEntryAtIndex:indexPath]
+ browserState:_browserState];
+ subview.reset([genericTabData.get() retain]);
+ break;
+ }
+ case CELL_SHOW_FULL_HISTORY:
+ subview.reset([[ShowFullHistoryView alloc] initWithFrame:CGRectZero]);
+ break;
+ case CELL_SEPARATOR:
+ subview.reset(
+ [[RecentlyClosedSectionFooter alloc] initWithFrame:CGRectZero]);
+ [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
+ break;
+ case CELL_OTHER_DEVICES_SECTION_HEADER: {
+ BOOL collapsed = [self sectionIsCollapsed:kOtherDeviceCollapsedKey];
+ subview.reset([[GenericSectionHeaderView alloc]
+ initWithType:recent_tabs::OTHER_DEVICES_SECTION_HEADER
+ sectionIsCollapsed:collapsed]);
+ [subview setTag:kSectionHeader];
+ break;
+ }
+ case CELL_OTHER_DEVICES_SIGNED_OUT:
+ subview.reset([[SignedOutView alloc] initWithFrame:CGRectZero]);
+ [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
+ break;
+ case CELL_OTHER_DEVICES_SIGNED_IN_SYNC_OFF:
+ subview.reset([[SignedInSyncOffView alloc] initWithFrame:CGRectZero
+ browserState:_browserState]);
+ [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
+ break;
+ case CELL_OTHER_DEVICES_SIGNED_IN_SYNC_ON_NO_SESSIONS:
+ subview.reset(
+ [[SignedInSyncOnNoSessionsView alloc] initWithFrame:CGRectZero]);
+ [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
+ break;
+ case CELL_OTHER_DEVICES_SYNC_IN_PROGRESS:
+ subview.reset(
+ [[SignedInSyncInProgressView alloc] initWithFrame:CGRectZero]);
+ [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
+ break;
+ case CELL_SESSION_SECTION_HEADER: {
+ synced_sessions::DistantSession const* distantSession =
+ [self sessionAtIndexPath:indexPath];
+ NSString* key = [self keyForDistantSession:distantSession];
+ BOOL collapsed = [self sectionIsCollapsed:key];
+ base::scoped_nsobject<SessionSectionHeaderView> sessionSectionHeader(
+ [[SessionSectionHeaderView alloc] initWithFrame:CGRectZero
+ sectionIsCollapsed:collapsed]);
+ [sessionSectionHeader updateWithSession:distantSession];
+ subview.reset(sessionSectionHeader.release());
+ [subview setTag:kSectionHeader];
+ break;
+ }
+ case CELL_SESSION_TAB_DATA: {
+ base::scoped_nsobject<SessionTabDataView> genericTabData(
+ [[SessionTabDataView alloc] initWithFrame:CGRectZero]);
+ [genericTabData updateWithDistantTab:[self distantTabAtIndex:indexPath]
+ browserState:_browserState];
+ subview.reset([genericTabData.get() retain]);
+ break;
+ }
+ }
+
+ DCHECK(subview);
+ [contentView addSubview:subview];
+
+ // Sets constraints on the subview.
+ [subview setTranslatesAutoresizingMaskIntoConstraints:NO];
+
+ NSDictionary* viewsDictionary = @{ @"view" : subview.get() };
+ // This set of constraints should match the constraints set on the
+ // RecentlyClosedSectionFooter.
+ // clang-format off
+ NSArray* constraints = @[
+ @"V:|-0-[view]-0-|",
+ @"H:|-(>=0)-[view(<=548)]-(>=0)-|",
+ @"H:[view(==548@500)]"
+ ];
+ // clang-format on
+ [contentView addConstraint:[NSLayoutConstraint
+ constraintWithItem:subview
+ attribute:NSLayoutAttributeCenterX
+ relatedBy:NSLayoutRelationEqual
+ toItem:contentView
+ attribute:NSLayoutAttributeCenterX
+ multiplier:1
+ constant:0]];
+ ApplyVisualConstraints(constraints, viewsDictionary, contentView);
+ return cell;
+}
+
+#pragma mark - UITableViewDelegate
+
+- (NSIndexPath*)tableView:(UITableView*)tableView
+ willSelectRowAtIndexPath:(NSIndexPath*)indexPath {
+ DCHECK_EQ(tableView, self.tableView);
+ CellType cellType = [self cellType:indexPath];
+ switch (cellType) {
+ case CELL_CLOSED_TAB_SECTION_HEADER:
+ case CELL_OTHER_DEVICES_SECTION_HEADER:
+ case CELL_SESSION_SECTION_HEADER:
+ case CELL_CLOSED_TAB_DATA:
+ case CELL_SESSION_TAB_DATA:
+ case CELL_SHOW_FULL_HISTORY:
+ return indexPath;
+ case CELL_SEPARATOR:
+ case CELL_OTHER_DEVICES_SIGNED_OUT:
+ case CELL_OTHER_DEVICES_SIGNED_IN_SYNC_OFF:
+ case CELL_OTHER_DEVICES_SIGNED_IN_SYNC_ON_NO_SESSIONS:
+ case CELL_OTHER_DEVICES_SYNC_IN_PROGRESS:
+ return nil;
+ }
+}
+
+- (void)tableView:(UITableView*)tableView
+ didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
+ DCHECK_EQ(tableView, self.tableView);
+ CellType cellType = [self cellType:indexPath];
+ switch (cellType) {
+ case CELL_CLOSED_TAB_SECTION_HEADER:
+ case CELL_OTHER_DEVICES_SECTION_HEADER:
+ case CELL_SESSION_SECTION_HEADER:
+ // Collapse or uncollapse section.
+ [tableView deselectRowAtIndexPath:indexPath animated:NO];
+ [self toggleExpansionOfSection:indexPath.section];
+ break;
+ case CELL_CLOSED_TAB_DATA:
+ // Open new tab.
+ [self openTabWithTabRestoreEntry:[self tabRestoreEntryAtIndex:indexPath]];
+ break;
+ case CELL_SESSION_TAB_DATA:
+ // Open new tab.
+ [self openTabWithContentOfDistantTab:[self distantTabAtIndex:indexPath]];
+ break;
+ case CELL_SHOW_FULL_HISTORY:
+ [tableView deselectRowAtIndexPath:indexPath animated:NO];
+ [self showFullHistory];
+ break;
+ case CELL_SEPARATOR:
+ case CELL_OTHER_DEVICES_SIGNED_OUT:
+ case CELL_OTHER_DEVICES_SIGNED_IN_SYNC_OFF:
+ case CELL_OTHER_DEVICES_SIGNED_IN_SYNC_ON_NO_SESSIONS:
+ case CELL_OTHER_DEVICES_SYNC_IN_PROGRESS:
+ NOTREACHED();
+ break;
+ }
+}
+
+- (CGFloat)tableView:(UITableView*)tableView
+ heightForRowAtIndexPath:(NSIndexPath*)indexPath {
+ DCHECK_EQ(self.tableView, tableView);
+ CellType cellType = [self cellType:indexPath];
+ switch (cellType) {
+ case CELL_SHOW_FULL_HISTORY:
+ return [ShowFullHistoryView desiredHeightInUITableViewCell];
+ case CELL_SEPARATOR:
+ return [RecentlyClosedSectionFooter desiredHeightInUITableViewCell];
+ case CELL_OTHER_DEVICES_SIGNED_OUT:
+ return [SignedOutView desiredHeightInUITableViewCell];
+ case CELL_OTHER_DEVICES_SIGNED_IN_SYNC_OFF:
+ return [SignedInSyncOffView desiredHeightInUITableViewCell];
+ case CELL_OTHER_DEVICES_SIGNED_IN_SYNC_ON_NO_SESSIONS:
+ return [SignedInSyncOnNoSessionsView desiredHeightInUITableViewCell];
+ case CELL_SESSION_SECTION_HEADER:
+ return [SessionSectionHeaderView desiredHeightInUITableViewCell];
+ case CELL_CLOSED_TAB_DATA:
+ case CELL_SESSION_TAB_DATA:
+ return [SessionTabDataView desiredHeightInUITableViewCell];
+ case CELL_CLOSED_TAB_SECTION_HEADER:
+ case CELL_OTHER_DEVICES_SECTION_HEADER:
+ return [GenericSectionHeaderView desiredHeightInUITableViewCell];
+ case CELL_OTHER_DEVICES_SYNC_IN_PROGRESS:
+ return [SignedInSyncInProgressView desiredHeightInUITableViewCell];
+ }
+}
+
+- (UIView*)tableView:(UITableView*)tableView
+ viewForHeaderInSection:(NSInteger)section {
+ if ([self sectionType:section] == CLOSED_TAB_SECTION) {
+ return [[[RecentlyTabsTopSpacingHeader alloc] initWithFrame:CGRectZero]
+ autorelease];
+ }
+ return nil;
+}
+
+- (CGFloat)tableView:(UITableView*)tableView
+ heightForHeaderInSection:(NSInteger)section {
+ if ([self sectionType:section] == CLOSED_TAB_SECTION) {
+ return [RecentlyTabsTopSpacingHeader desiredHeightInUITableViewCell];
+ }
+ return 0;
+}
+
+#pragma mark - UIScrollViewDelegate
+
+- (void)scrollViewDidScroll:(UIScrollView*)scrollView {
+ [delegate_ recentTabsTableViewContentMoved:self.tableView];
+}
+
+@end

Powered by Google App Engine
This is Rietveld 408576698