Commit 2850e6e4 authored by Robbie Gibson's avatar Robbie Gibson Committed by Commit Bot

[iOS] Thumb Strip: tab switcher button uses thumb strip transitions

When the thumb strip is in hidden or peeked state, the tab switcher
button will transition the thumb strip to revealed state.

This CL also adds a public method to ViewRevealingVerticalPanHandler to
set the state externally.

Finally, the CL does not support tapping on the tab switcher button when
the thumb strip is in revealed state. This is because the design has the
BVC covered in a shim in this state. This hasn't been implemented yet,
but the tab switcher button should be inaccessible.

Change-Id: Ic44ddd8dc676fbdeb787bc75c422f332a9405574
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2455648
Commit-Queue: Robbie Gibson <rkgibson@google.com>
Reviewed-by: default avatarGauthier Ambard <gambard@chromium.org>
Cr-Commit-Position: refs/heads/master@{#815226}
parent 80b563d7
......@@ -10,6 +10,19 @@
#import "ios/chrome/browser/ui/gestures/layout_switcher_provider.h"
#import "ios/chrome/browser/ui/gestures/view_revealing_animatee.h"
@class ViewRevealingVerticalPanHandler;
// Delegate for the ViewRevealingVerticalPanHandler to alert when it changes
// state.
@protocol ViewRevealingVerticalPanHandlerDelegate <NSObject>
// Informs the delegate that the pan handler did change state, ending at the
// given state.
- (void)viewRevealingVerticalPanHandler:
(ViewRevealingVerticalPanHandler*)panHandler
didChangeToState:(ViewRevealState)viewRevealState;
@end
// Responsible for handling vertical pan gestures to reveal/hide a view behind
// another.
// TODO(crbug.com/1123512): Add support for going straight from a Hidden state
......@@ -35,6 +48,11 @@
// to the animatees.
- (void)addAnimatee:(id<ViewRevealingAnimatee>)animatee;
// Manually sets the state of the pan handler to a specific state, optionally
// animated.
- (void)setState:(ViewRevealState)state animated:(BOOL)animated;
@property(nonatomic, weak) id<ViewRevealingVerticalPanHandlerDelegate> delegate;
// Height of the view that will be revealed after the transition to Peeked
// state.
@property(nonatomic, assign, readonly) CGFloat peekedHeight;
......
......@@ -106,6 +106,19 @@ const CGFloat kAnimationDuration = 0.25f;
_remainingHeight = _revealedHeight - _peekedHeight;
}
- (void)setState:(ViewRevealState)state animated:(BOOL)animated {
self.nextState = state;
[self createAnimatorIfNeeded];
if (animated) {
[self.animator startAnimation];
} else {
[self.animator setFractionComplete:1];
[self.animator stopAnimation:NO];
[self.animator finishAnimationAtPosition:UIViewAnimatingPositionEnd];
}
[self completeLayoutTransitionSuccessfully:YES];
}
#pragma mark - Private Methods: Animating
// Called right before an animation block to warn all animatees of a transition
......@@ -127,6 +140,8 @@ const CGFloat kAnimationDuration = 0.25f;
// Called inside the completion block of the current animation. Takes as
// argument the state to which the animatees did animate to.
- (void)didAnimateViewReveal:(ViewRevealState)viewRevealState {
[self.delegate viewRevealingVerticalPanHandler:self
didChangeToState:viewRevealState];
for (id<ViewRevealingAnimatee> animatee in self.animatees) {
[animatee didAnimateViewReveal:viewRevealState];
}
......@@ -170,11 +185,11 @@ const CGFloat kAnimationDuration = 0.25f;
return;
}
if (self.currentState == ViewRevealState::Peeked &&
self.nextState == ViewRevealState::Revealed) {
if (self.nextState == ViewRevealState::Revealed) {
[self willTransitionToLayout:LayoutSwitcherState::Full];
} else if (self.currentState == ViewRevealState::Revealed &&
self.nextState == ViewRevealState::Peeked) {
(self.nextState == ViewRevealState::Peeked ||
self.nextState == ViewRevealState::Hidden)) {
[self willTransitionToLayout:LayoutSwitcherState::Horizontal];
}
}
......@@ -329,9 +344,15 @@ const CGFloat kAnimationDuration = 0.25f;
[self.animator continueAnimationWithTimingParameters:nil durationFactor:1];
[self completeLayoutTransitionSuccessfully:!self.animator.reversed];
}
// If the layout is currently changing, tells the layout provider to
// finish the transition.
- (void)completeLayoutTransitionSuccessfully:(BOOL)success {
if (self.layoutBeingInteractedWith) {
[self.layoutSwitcherProvider.layoutSwitcher
didTransitionToLayoutSuccessfully:!self.animator.reversed];
didTransitionToLayoutSuccessfully:success];
self.gesturesEnabled = NO;
self.layoutBeingInteractedWith = NO;
}
......
......@@ -198,4 +198,36 @@ TEST_F(ViewRevealingVerticalPanHandlerTest, DetectPan) {
SimulatePanGesture(pan_handler, -(kThumbStripHeight * kRevealThreshold));
EXPECT_EQ(ViewRevealState::Hidden, fake_animatee.state);
}
// Tests that manually moving the pan handler between the two outer-most states
// updates everything correctly.
TEST_F(ViewRevealingVerticalPanHandlerTest, ManualStateChange) {
// Create a view revealing vertical pan handler.
ViewRevealingVerticalPanHandler* pan_handler =
[[ViewRevealingVerticalPanHandler alloc]
initWithPeekedHeight:kThumbStripHeight
revealedCoverHeight:kBVCHeightTabGrid
baseViewHeight:kBaseViewHeight];
// Create a fake layout switcher and a provider.
FakeLayoutSwitcher* fake_layout_switcher = [[FakeLayoutSwitcher alloc] init];
FakeLayoutSwitcherProvider* fake_layout_switcher_provider =
[[FakeLayoutSwitcherProvider alloc]
initWithLayoutSwitcher:fake_layout_switcher];
pan_handler.layoutSwitcherProvider = fake_layout_switcher_provider;
EXPECT_EQ(LayoutSwitcherState::Horizontal, fake_layout_switcher.state);
// Create a fake animatee.
FakeAnimatee* fake_animatee = [[FakeAnimatee alloc] init];
[pan_handler addAnimatee:fake_animatee];
EXPECT_EQ(ViewRevealState::Hidden, fake_animatee.state);
[pan_handler setState:ViewRevealState::Revealed animated:NO];
EXPECT_EQ(ViewRevealState::Revealed, fake_animatee.state);
EXPECT_EQ(LayoutSwitcherState::Full, fake_layout_switcher.state);
[pan_handler setState:ViewRevealState::Hidden animated:NO];
EXPECT_EQ(ViewRevealState::Hidden, fake_animatee.state);
EXPECT_EQ(LayoutSwitcherState::Horizontal, fake_layout_switcher.state);
}
} // namespace
......@@ -57,7 +57,8 @@
RecentTabsContextMenuDelegate,
RecentTabsPresentationDelegate,
TabGridMediatorDelegate,
TabPresentationDelegate> {
TabPresentationDelegate,
ThumbStripCoordinatorDelegate> {
// Use an explicit ivar instead of synthesizing as the setter isn't using the
// ivar.
Browser* _incognitoBrowser;
......@@ -181,6 +182,12 @@
- (void)showTabGrid {
BOOL animated = !self.animationsDisabledForTesting;
if (IsThumbStripEnabled()) {
[self.thumbStripCoordinator.panHandler setState:ViewRevealState::Revealed
animated:animated];
return;
}
// If a BVC is currently being presented, dismiss it. This will trigger any
// necessary animations.
if (self.bvcContainer) {
......@@ -214,6 +221,22 @@
// Record when the tab switcher is dismissed.
base::RecordAction(base::UserMetricsAction("MobileTabGridExited"));
// If thumb strip is enabled, this will always be true except during initial
// setup before the BVC container has been created.
if (IsThumbStripEnabled() && self.bvcContainer) {
self.bvcContainer.currentBVC = viewController;
self.baseViewController.childViewControllerForStatusBarStyle =
viewController;
[self.baseViewController setNeedsStatusBarAppearanceUpdate];
[self.thumbStripCoordinator.panHandler setState:ViewRevealState::Hidden
animated:YES];
if (completion) {
completion();
}
[self.delegate tabGridDismissTransitionDidEnd:self];
return;
}
// If another BVC is already being presented, swap this one into the
// container.
if (self.bvcContainer) {
......@@ -349,6 +372,7 @@
self.thumbStripCoordinator = [[ThumbStripCoordinator alloc]
initWithBaseViewController:baseViewController
browser:self.browser];
self.thumbStripCoordinator.delegate = self;
[self.thumbStripCoordinator start];
self.thumbStripCoordinator.panHandler.layoutSwitcherProvider =
baseViewController;
......@@ -554,6 +578,13 @@
sessionForSectionIdentifier:sectionIdentifier];
}
#pragma mark - ThumbStripCoordinatorDelegate
- (void)thumbStripDismissedForThumbStripCoordinator:
(ThumbStripCoordinator*)thumbStripCoordinator {
[self.delegate tabGridDismissTransitionDidEnd:self];
}
#pragma mark - Private methods
- (void)setUpThumbStripAttachers {
......
......@@ -9,12 +9,25 @@
#import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
@class ThumbStripCoordinator;
@class ViewRevealingVerticalPanHandler;
// Delegate for the ThumbStripCoordinator to alert when it is dismissed.
@protocol ThumbStripCoordinatorDelegate
// Informs the delegate that the thumb strip has been dismissed. I.e. was
// showing and is now not showing.
- (void)thumbStripDismissedForThumbStripCoordinator:
(ThumbStripCoordinator*)thumbStripCoordinator;
@end
// Coordinator for the thumb strip, which is a 1-row horizontal display of tab
// miniatures above the toolbar.
@interface ThumbStripCoordinator : ChromeCoordinator
@property(nonatomic, weak) id<ThumbStripCoordinatorDelegate> delegate;
// The thumb strip's pan gesture handler.
@property(nonatomic, strong) ViewRevealingVerticalPanHandler* panHandler;
......
......@@ -21,6 +21,10 @@ const CGFloat kThumbStripHeight = 168.0f + 22.0f + 22.0f;
const CGFloat kBVCHeightTabGrid = 108.0f;
} // namespace
@interface ThumbStripCoordinator () <ViewRevealingVerticalPanHandlerDelegate>
@end
@implementation ThumbStripCoordinator
#pragma mark - ChromeCoordinator
......@@ -31,10 +35,21 @@ const CGFloat kBVCHeightTabGrid = 108.0f;
initWithPeekedHeight:kThumbStripHeight
revealedCoverHeight:kBVCHeightTabGrid
baseViewHeight:baseViewHeight];
self.panHandler.delegate = self;
}
- (void)stop {
self.panHandler = nil;
}
#pragma mark - ViewRevealingVerticalPanHandlerDelegate
- (void)viewRevealingVerticalPanHandler:
(ViewRevealingVerticalPanHandler*)panHandler
didChangeToState:(ViewRevealState)viewRevealState {
if (viewRevealState == ViewRevealState::Hidden) {
[self.delegate thumbStripDismissedForThumbStripCoordinator:self];
}
}
@end
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