Commit 82fe4dde authored by sczs's avatar sczs Committed by Commit Bot

[ios] Adds collapse and context menu functionality to Recent Tabs Headers

- Adds expand/collapse functionality.
- Adds contextmenu functionality (Open All and Hide For now).
- Adds a tapGesture recognizer for headers.

Video:
https://drive.google.com/open?id=1LwJCMrUlNmJ73O_SWHEpImBqaEh4M1hv

Bug: 819462
Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: Idc51238b5cfb205f3150d33364efad83868c7333
Reviewed-on: https://chromium-review.googlesource.com/958099
Commit-Queue: Sergio Collazos <sczs@chromium.org>
Reviewed-by: default avatarRohit Rao <rohitrao@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543535}
parent 0b93fef0
...@@ -96,6 +96,8 @@ const int kRelativeTimeMaxHours = 4; ...@@ -96,6 +96,8 @@ const int kRelativeTimeMaxHours = 4;
// Handles displaying the context menu for all form factors. // Handles displaying the context menu for all form factors.
@property(nonatomic, strong) ContextMenuCoordinator* contextMenuCoordinator; @property(nonatomic, strong) ContextMenuCoordinator* contextMenuCoordinator;
@property(nonatomic, strong) SigninPromoViewMediator* signinPromoViewMediator; @property(nonatomic, strong) SigninPromoViewMediator* signinPromoViewMediator;
// The sectionIdentifier for the last tapped header, 0 if no header was tapped.
@property(nonatomic, assign) NSInteger lastTappedHeaderSectionIdentifier;
@end @end
@implementation RecentTabsTableViewController : ChromeTableViewController @implementation RecentTabsTableViewController : ChromeTableViewController
...@@ -104,6 +106,8 @@ const int kRelativeTimeMaxHours = 4; ...@@ -104,6 +106,8 @@ const int kRelativeTimeMaxHours = 4;
@synthesize delegate = delegate_; @synthesize delegate = delegate_;
@synthesize dispatcher = _dispatcher; @synthesize dispatcher = _dispatcher;
@synthesize handsetCommandHandler = _handsetCommandHandler; @synthesize handsetCommandHandler = _handsetCommandHandler;
@synthesize lastTappedHeaderSectionIdentifier =
_lastTappedHeaderSectionIdentifier;
@synthesize loader = _loader; @synthesize loader = _loader;
@synthesize sessionState = _sessionState; @synthesize sessionState = _sessionState;
@synthesize signinPromoViewMediator = _signinPromoViewMediator; @synthesize signinPromoViewMediator = _signinPromoViewMediator;
...@@ -116,6 +120,7 @@ const int kRelativeTimeMaxHours = 4; ...@@ -116,6 +120,7 @@ const int kRelativeTimeMaxHours = 4;
if (self) { if (self) {
_sessionState = SessionsSyncUserState::USER_SIGNED_OUT; _sessionState = SessionsSyncUserState::USER_SIGNED_OUT;
_syncedSessions.reset(new synced_sessions::SyncedSessions()); _syncedSessions.reset(new synced_sessions::SyncedSessions());
_lastTappedHeaderSectionIdentifier = 0;
} }
return self; return self;
} }
...@@ -134,12 +139,19 @@ const int kRelativeTimeMaxHours = 4; ...@@ -134,12 +139,19 @@ const int kRelativeTimeMaxHours = 4;
self.tableView.rowHeight = UITableViewAutomaticDimension; self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedSectionHeaderHeight = kEstimatedRowHeight; self.tableView.estimatedSectionHeaderHeight = kEstimatedRowHeight;
self.tableView.sectionFooterHeight = 0.0; self.tableView.sectionFooterHeight = 0.0;
// Gesture recognizer for long press context menu on Session Headers.
UILongPressGestureRecognizer* longPress = UILongPressGestureRecognizer* longPress =
[[UILongPressGestureRecognizer alloc] [[UILongPressGestureRecognizer alloc]
initWithTarget:self initWithTarget:self
action:@selector(handleLongPress:)]; action:@selector(handleLongPress:)];
longPress.delegate = self; longPress.delegate = self;
[self.tableView addGestureRecognizer:longPress]; [self.tableView addGestureRecognizer:longPress];
// Gesture recognizer for header collapsing/expanding.
UITapGestureRecognizer* tapGesture =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(handleTap:)];
tapGesture.delegate = self;
[self.tableView addGestureRecognizer:tapGesture];
} }
#pragma mark - TableViewModel #pragma mark - TableViewModel
...@@ -166,6 +178,8 @@ const int kRelativeTimeMaxHours = 4; ...@@ -166,6 +178,8 @@ const int kRelativeTimeMaxHours = 4;
// Recently Closed Section. // Recently Closed Section.
[model addSectionWithIdentifier:SectionIdentifierRecentlyClosedTabs]; [model addSectionWithIdentifier:SectionIdentifierRecentlyClosedTabs];
[model setSectionIdentifier:SectionIdentifierRecentlyClosedTabs
collapsedKey:kRecentlyClosedCollapsedKey];
TableViewTextHeaderFooterItem* header = [[TableViewTextHeaderFooterItem alloc] TableViewTextHeaderFooterItem* header = [[TableViewTextHeaderFooterItem alloc]
initWithType:ItemTypeRecentlyClosedHeader]; initWithType:ItemTypeRecentlyClosedHeader];
header.text = l10n_util::GetNSString(IDS_IOS_RECENT_TABS_RECENTLY_CLOSED); header.text = l10n_util::GetNSString(IDS_IOS_RECENT_TABS_RECENTLY_CLOSED);
...@@ -271,6 +285,9 @@ const int kRelativeTimeMaxHours = 4; ...@@ -271,6 +285,9 @@ const int kRelativeTimeMaxHours = 4;
_syncedSessions->GetSession(i); _syncedSessions->GetSession(i);
NSInteger sessionIdentifier = [self sectionIdentifierForSession:session]; NSInteger sessionIdentifier = [self sectionIdentifierForSession:session];
[model addSectionWithIdentifier:sessionIdentifier]; [model addSectionWithIdentifier:sessionIdentifier];
NSString* sessionCollapsedKey = base::SysUTF8ToNSString(session->tag);
[model setSectionIdentifier:sessionIdentifier
collapsedKey:sessionCollapsedKey];
TableViewTextHeaderFooterItem* header = TableViewTextHeaderFooterItem* header =
[[TableViewTextHeaderFooterItem alloc] [[TableViewTextHeaderFooterItem alloc]
initWithType:ItemTypeSessionHeader]; initWithType:ItemTypeSessionHeader];
...@@ -305,18 +322,24 @@ const int kRelativeTimeMaxHours = 4; ...@@ -305,18 +322,24 @@ const int kRelativeTimeMaxHours = 4;
// Needs to be called inside a [UITableView beginUpdates] block on iOS10, or // Needs to be called inside a [UITableView beginUpdates] block on iOS10, or
// performBatchUpdates on iOS11+. // performBatchUpdates on iOS11+.
- (void)removeSessionSections { - (void)removeSessionSections {
// Get the numberOfSessionSections from |self.tableViewModel|, since // |_syncedSessions| has been updated by now, that means that
// |_syncedSessions| has been updated by now. // |self.tableViewModel| does not reflect |_syncedSessions| data. A
int numberOfSessionSections = // SectionIdentifier could've been deleted previously, do not rely on these
[self.tableViewModel numberOfSections] - kNumberOfSectionsBeforeSessions; // being in sequential order at this point.
for (int i = 0; i < numberOfSessionSections; i++) { NSInteger sectionIdentifierToRemove = kFirstSessionSectionIdentifier;
[self.tableView NSInteger sectionToDelete = kNumberOfSectionsBeforeSessions;
deleteSections:[NSIndexSet while ([self.tableViewModel numberOfSections] >
indexSetWithIndex:i + kNumberOfSectionsBeforeSessions) {
kNumberOfSectionsBeforeSessions] if ([self.tableViewModel
withRowAnimation:UITableViewRowAnimationNone]; hasSectionForSectionIdentifier:sectionIdentifierToRemove]) {
[self.tableViewModel [self.tableView
removeSectionWithIdentifier:i + kFirstSessionSectionIdentifier]; deleteSections:[NSIndexSet indexSetWithIndex:sectionToDelete]
withRowAnimation:UITableViewRowAnimationNone];
sectionToDelete++;
[self.tableViewModel
removeSectionWithIdentifier:sectionIdentifierToRemove];
}
sectionIdentifierToRemove++;
} }
} }
...@@ -355,6 +378,8 @@ const int kRelativeTimeMaxHours = 4; ...@@ -355,6 +378,8 @@ const int kRelativeTimeMaxHours = 4;
- (void)addOtherDevicesSectionHeader { - (void)addOtherDevicesSectionHeader {
TableViewModel* model = self.tableViewModel; TableViewModel* model = self.tableViewModel;
[model addSectionWithIdentifier:SectionIdentifierOtherDevices]; [model addSectionWithIdentifier:SectionIdentifierOtherDevices];
[model setSectionIdentifier:SectionIdentifierOtherDevices
collapsedKey:kOtherDeviceCollapsedKey];
TableViewTextHeaderFooterItem* header = [[TableViewTextHeaderFooterItem alloc] TableViewTextHeaderFooterItem* header = [[TableViewTextHeaderFooterItem alloc]
initWithType:ItemTypeRecentlyClosedHeader]; initWithType:ItemTypeRecentlyClosedHeader];
header.text = l10n_util::GetNSString(IDS_IOS_RECENT_TABS_OTHER_DEVICES); header.text = l10n_util::GetNSString(IDS_IOS_RECENT_TABS_OTHER_DEVICES);
...@@ -605,6 +630,10 @@ const int kRelativeTimeMaxHours = 4; ...@@ -605,6 +630,10 @@ const int kRelativeTimeMaxHours = 4;
[self indexOfSessionForTabAtIndexPath:indexPath]); [self indexOfSessionForTabAtIndexPath:indexPath]);
} }
- (synced_sessions::DistantSession const*)sessionForSection:(NSInteger)section {
return _syncedSessions->GetSession(section - kNumberOfSectionsBeforeSessions);
}
- (synced_sessions::DistantTab const*)distantTabAtIndexPath: - (synced_sessions::DistantTab const*)distantTabAtIndexPath:
(NSIndexPath*)indexPath { (NSIndexPath*)indexPath {
DCHECK_EQ([self.tableViewModel itemTypeForIndexPath:indexPath], DCHECK_EQ([self.tableViewModel itemTypeForIndexPath:indexPath],
...@@ -705,34 +734,48 @@ const int kRelativeTimeMaxHours = 4; ...@@ -705,34 +734,48 @@ const int kRelativeTimeMaxHours = 4;
[self.handsetCommandHandler dismissRecentTabsWithCompletion:openHistory]; [self.handsetCommandHandler dismissRecentTabsWithCompletion:openHistory];
} }
#pragma mark - Collapse sections #pragma mark - Collapse/Expand sections
- (void)toggleExpansionOfSection:(NSInteger)sectionIndex { - (void)handleTap:(UITapGestureRecognizer*)tapGesture {
// TO IMPLEMENT! DCHECK_EQ(self.tableView, tapGesture.view);
if (tapGesture.state == UIGestureRecognizerStateEnded) {
[self toggleExpansionOfSectionIdentifier:
self.lastTappedHeaderSectionIdentifier];
}
} }
- (void)setSection:(NSString*)sectionKey collapsed:(BOOL)collapsed { - (void)toggleExpansionOfSectionIdentifier:(NSInteger)sectionIdentifier {
// TODO(crbug.com/419346): Store in the browser state preference instead of NSMutableArray* cellIndexPathsToDeleteOrInsert = [NSMutableArray array];
// NSUserDefaults. NSInteger sectionIndex =
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; [self.tableViewModel sectionForSectionIdentifier:sectionIdentifier];
NSDictionary* collapsedSections = NSArray* items =
[defaults dictionaryForKey:kCollapsedSectionsKey]; [self.tableViewModel itemsInSectionWithIdentifier:sectionIdentifier];
NSMutableDictionary* newCollapsedSessions = for (NSUInteger i = 0; i < [items count]; i++) {
[NSMutableDictionary dictionaryWithDictionary:collapsedSections]; NSIndexPath* tabIndexPath =
NSNumber* value = [NSNumber numberWithBool:collapsed]; [NSIndexPath indexPathForRow:i inSection:sectionIndex];
[newCollapsedSessions setValue:value forKey:sectionKey]; [cellIndexPathsToDeleteOrInsert addObject:tabIndexPath];
[defaults setObject:newCollapsedSessions forKey:kCollapsedSectionsKey]; }
}
- (BOOL)sectionIsCollapsed:(NSString*)sectionKey { void (^tableUpdates)(void) = ^{
// TODO(crbug.com/419346): Store in the profile's preference instead of the if ([self.tableViewModel sectionIsCollapsed:sectionIdentifier]) {
// NSUserDefaults. [self.tableViewModel setSection:sectionIdentifier collapsed:NO];
DCHECK(sectionKey); [self.tableView insertRowsAtIndexPaths:cellIndexPathsToDeleteOrInsert
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; withRowAnimation:UITableViewRowAnimationFade];
NSDictionary* collapsedSessions = } else {
[defaults dictionaryForKey:kCollapsedSectionsKey]; [self.tableViewModel setSection:sectionIdentifier collapsed:YES];
NSNumber* value = (NSNumber*)[collapsedSessions valueForKey:sectionKey]; [self.tableView deleteRowsAtIndexPaths:cellIndexPathsToDeleteOrInsert
return [value boolValue]; withRowAnimation:UITableViewRowAnimationFade];
}
};
// If iOS11+ use performBatchUpdates: instead of beginUpdates/endUpdates.
if (@available(iOS 11, *)) {
[self.tableView performBatchUpdates:tableUpdates completion:nil];
} else {
[self.tableView beginUpdates];
tableUpdates();
[self.tableView endUpdates];
}
} }
#pragma mark - Long press and context menus #pragma mark - Long press and context menus
...@@ -740,16 +783,9 @@ const int kRelativeTimeMaxHours = 4; ...@@ -740,16 +783,9 @@ const int kRelativeTimeMaxHours = 4;
- (void)handleLongPress:(UILongPressGestureRecognizer*)longPressGesture { - (void)handleLongPress:(UILongPressGestureRecognizer*)longPressGesture {
DCHECK_EQ(self.tableView, longPressGesture.view); DCHECK_EQ(self.tableView, longPressGesture.view);
if (longPressGesture.state == UIGestureRecognizerStateBegan) { if (longPressGesture.state == UIGestureRecognizerStateBegan) {
CGPoint point = [longPressGesture locationInView:self.tableView]; NSInteger sectionIdentifier = self.lastTappedHeaderSectionIdentifier;
NSIndexPath* indexPath = [self.tableView indexPathForRowAtPoint:point]; if (![self isSessionSectionIdentifier:sectionIdentifier]) {
if (!indexPath) // Only handle LongPress for SessionHeaders.
return;
DCHECK_LE(indexPath.section,
[self numberOfSectionsInTableView:self.tableView]);
NSInteger itemType = [self.tableViewModel itemTypeForIndexPath:indexPath];
if ([self isSessionSectionIdentifier:itemType]) {
NOTREACHED();
return; return;
} }
...@@ -777,7 +813,8 @@ const int kRelativeTimeMaxHours = 4; ...@@ -777,7 +813,8 @@ const int kRelativeTimeMaxHours = 4;
[self.contextMenuCoordinator [self.contextMenuCoordinator
addItemWithTitle:openAllButtonLabel addItemWithTitle:openAllButtonLabel
action:^{ action:^{
[weakSelf openTabsFromSessionAtIndexPath:indexPath]; [weakSelf
openTabsFromSessionSectionIdentifier:sectionIdentifier];
}]; }];
// "Hide for now" button. // "Hide for now" button.
...@@ -786,18 +823,19 @@ const int kRelativeTimeMaxHours = 4; ...@@ -786,18 +823,19 @@ const int kRelativeTimeMaxHours = 4;
[self.contextMenuCoordinator [self.contextMenuCoordinator
addItemWithTitle:hideButtonLabel addItemWithTitle:hideButtonLabel
action:^{ action:^{
[weakSelf removeSessionAtIndexPath:indexPath]; [weakSelf removeSessionAtSessionSectionIdentifier:
sectionIdentifier];
}]; }];
[self.contextMenuCoordinator start]; [self.contextMenuCoordinator start];
} }
} }
- (void)openTabsFromSessionAtIndexPath:(NSIndexPath*)indexPath { - (void)openTabsFromSessionSectionIdentifier:(NSInteger)sectionIdentifier {
// TO-ADAPT. NSInteger section =
return; [self.tableViewModel sectionForSectionIdentifier:sectionIdentifier];
synced_sessions::DistantSession const* session = synced_sessions::DistantSession const* session =
[self sessionForTabAtIndexPath:indexPath]; [self sessionForSection:section];
[self dismissRecentTabsModal]; [self dismissRecentTabsModal];
for (auto const& tab : session->tabs) { for (auto const& tab : session->tabs) {
[self.loader webPageOrderedOpen:tab->virtual_url [self.loader webPageOrderedOpen:tab->virtual_url
...@@ -807,29 +845,45 @@ const int kRelativeTimeMaxHours = 4; ...@@ -807,29 +845,45 @@ const int kRelativeTimeMaxHours = 4;
} }
} }
- (void)removeSessionAtIndexPath:(NSIndexPath*)indexPath { - (void)removeSessionAtSessionSectionIdentifier:(NSInteger)sectionIdentifier {
// TO-ADAPT. DCHECK([self isSessionSectionIdentifier:sectionIdentifier]);
return; NSInteger section =
DCHECK([self [self.tableViewModel sectionForSectionIdentifier:sectionIdentifier];
isSessionSectionIdentifier:
[self.tableViewModel sectionIdentifierForSection:indexPath.section]]);
synced_sessions::DistantSession const* session = synced_sessions::DistantSession const* session =
[self sessionForTabAtIndexPath:indexPath]; [self sessionForSection:section];
std::string sessionTagCopy = session->tag; std::string sessionTagCopy = session->tag;
syncer::SyncService* syncService = syncer::SyncService* syncService =
IOSChromeProfileSyncServiceFactory::GetForBrowserState(self.browserState); IOSChromeProfileSyncServiceFactory::GetForBrowserState(self.browserState);
sync_sessions::OpenTabsUIDelegate* openTabs = sync_sessions::OpenTabsUIDelegate* openTabs =
syncService->GetOpenTabsUIDelegate(); syncService->GetOpenTabsUIDelegate();
_syncedSessions->EraseSession(
[self indexOfSessionForTabAtIndexPath:indexPath]); [self.tableViewModel removeSectionWithIdentifier:sectionIdentifier];
[self.tableView _syncedSessions->EraseSession(sectionIdentifier -
deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section] kFirstSessionSectionIdentifier);
withRowAnimation:UITableViewRowAnimationLeft];
// Use dispatch_async to give the action sheet a chance to cleanup before void (^tableUpdates)(void) = ^{
// replacing its parent view. [self.tableView
dispatch_async(dispatch_get_main_queue(), ^{ deleteSections:[NSIndexSet
indexSetWithIndex:sectionIdentifier -
kSectionIdentifierEnumZero]
withRowAnimation:UITableViewRowAnimationLeft];
};
// If iOS11+ use performBatchUpdates: instead of beginUpdates/endUpdates.
if (@available(iOS 11, *)) {
[self.tableView performBatchUpdates:tableUpdates
completion:^(BOOL) {
openTabs->DeleteForeignSession(sessionTagCopy);
}];
} else {
[self.tableView beginUpdates];
tableUpdates();
// DeleteForeignSession will cause |self refreshUserState:| to be called,
// thus refreshing the TableView, running this inside the updates block will
// make sure that the tableView animations are performed in order.
openTabs->DeleteForeignSession(sessionTagCopy); openTabs->DeleteForeignSession(sessionTagCopy);
}); [self.tableView endUpdates];
}
} }
#pragma mark - UIGestureRecognizerDelegate #pragma mark - UIGestureRecognizerDelegate
...@@ -842,8 +896,8 @@ const int kRelativeTimeMaxHours = 4; ...@@ -842,8 +896,8 @@ const int kRelativeTimeMaxHours = 4;
NSInteger itemType = NSInteger itemType =
[self.tableViewModel sectionIdentifierForSection:section]; [self.tableViewModel sectionIdentifierForSection:section];
if (CGRectContainsPoint([self.tableView rectForHeaderInSection:section], if (CGRectContainsPoint([self.tableView rectForHeaderInSection:section],
point) && point)) {
[self isSessionSectionIdentifier:itemType]) { self.lastTappedHeaderSectionIdentifier = itemType;
return YES; return YES;
} }
} }
...@@ -856,7 +910,7 @@ const int kRelativeTimeMaxHours = 4; ...@@ -856,7 +910,7 @@ const int kRelativeTimeMaxHours = 4;
(SigninPromoViewConfigurator*)configurator (SigninPromoViewConfigurator*)configurator
identityChanged:(BOOL)identityChanged { identityChanged:(BOOL)identityChanged {
DCHECK(self.signinPromoViewMediator); DCHECK(self.signinPromoViewMediator);
if ([self sectionIsCollapsed:kOtherDeviceCollapsedKey]) if ([self.tableViewModel sectionIsCollapsed:SectionIdentifierOtherDevices])
return; return;
NSIndexPath* indexPath = NSIndexPath* indexPath =
[self.tableViewModel indexPathForItemType:ItemTypeOtherDevicesSigninPromo [self.tableViewModel indexPathForItemType:ItemTypeOtherDevicesSigninPromo
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment