| Index: ios/chrome/browser/ui/tab_switcher/tab_switcher_view.mm
|
| diff --git a/ios/chrome/browser/ui/tab_switcher/tab_switcher_view.mm b/ios/chrome/browser/ui/tab_switcher/tab_switcher_view.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..493133c4b8d69603a723ae83d9bfb39c0db90325
|
| --- /dev/null
|
| +++ b/ios/chrome/browser/ui/tab_switcher/tab_switcher_view.mm
|
| @@ -0,0 +1,368 @@
|
| +// 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_view.h"
|
| +
|
| +#include "base/logging.h"
|
| +#include "base/mac/foundation_util.h"
|
| +#include "base/mac/scoped_nsobject.h"
|
| +#include "base/metrics/user_metrics.h"
|
| +#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
|
| +#include "ios/chrome/browser/ui/rtl_geometry.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_header_view.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_controller.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"
|
| +#include "ios/chrome/grit/ios_strings.h"
|
| +#import "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.h"
|
| +#import "ios/third_party/material_components_ios/src/components/Palettes/src/MaterialPalettes.h"
|
| +#include "ui/base/l10n/l10n_util.h"
|
| +
|
| +namespace {
|
| +const CGFloat kHeaderHeight = 95;
|
| +const CGFloat kNewTabButtonMarginFromEdges = 48;
|
| +const CGFloat kNewTabButtonWidth = 48;
|
| +}
|
| +
|
| +@interface TabSwitcherView ()<UIScrollViewDelegate> {
|
| + base::scoped_nsobject<TabSwitcherHeaderView> _headerView;
|
| + base::scoped_nsobject<UIScrollView> _scrollView;
|
| + base::scoped_nsobject<MDCButton> _openNewTabButton;
|
| + base::scoped_nsobject<NSMutableArray> _panels;
|
| + ios_internal::NewTabButtonStyle _openNewTabButtonStyle;
|
| + NSInteger _previousPanelIndex;
|
| +}
|
| +
|
| +// Returns the header view frame.
|
| +- (CGRect)headerViewFrame;
|
| +// Returns the scrollview frame.
|
| +- (CGRect)scrollViewFrame;
|
| +// Returns the new tab button frame.
|
| +- (CGRect)openNewTabButtonFrame;
|
| +// Returns the new tab button frame when hidden.
|
| +- (CGRect)openNewTabButtonFrameOffscreen;
|
| +// Called when the new tab button is pressed.
|
| +- (void)openNewTabButtonPressed;
|
| +// Returns the index of the panel presented in |_scrollview|. When two panels
|
| +// are visible, it returns the most visible one.
|
| +- (NSInteger)currentPageIndex;
|
| +// Select the panel at the given index, updating both the header and content.
|
| +// The scrollview scroll will be |animated| if requested.
|
| +- (void)selectPanelAtIndex:(NSInteger)index animated:(BOOL)animated;
|
| +@end
|
| +
|
| +@implementation TabSwitcherView
|
| +
|
| +@synthesize delegate = delegate_;
|
| +
|
| +- (instancetype)initWithFrame:(CGRect)frame {
|
| + self = [super initWithFrame:frame];
|
| + if (self) {
|
| + _openNewTabButtonStyle = ios_internal::NewTabButtonStyle::UNINITIALIZED;
|
| + [self loadSubviews];
|
| + _panels.reset([[NSMutableArray alloc] init]);
|
| + _previousPanelIndex = -1;
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (TabSwitcherHeaderView*)headerView {
|
| + return _headerView;
|
| +}
|
| +
|
| +- (UIScrollView*)scrollView {
|
| + return _scrollView;
|
| +}
|
| +
|
| +- (void)layoutSubviews {
|
| + [super layoutSubviews];
|
| + NSInteger pageIndexBeforeLayout = [self currentPageIndex];
|
| + [CATransaction begin];
|
| + [CATransaction setDisableActions:YES];
|
| + [_headerView setFrame:[self headerViewFrame]];
|
| + [_scrollView setFrame:[self scrollViewFrame]];
|
| + [CATransaction commit];
|
| + [self updateScrollViewContent];
|
| + [self selectPanelAtIndex:pageIndexBeforeLayout animated:NO];
|
| +}
|
| +
|
| +- (void)selectPanelAtIndex:(NSInteger)index {
|
| + [self selectPanelAtIndex:index animated:!UIAccessibilityIsVoiceOverRunning()];
|
| +}
|
| +
|
| +- (void)addPanelView:(UIView*)view atIndex:(NSUInteger)index {
|
| + if (UseRTLLayout())
|
| + [view setTransform:CGAffineTransformMakeScale(-1, 1)];
|
| + [_scrollView addSubview:view];
|
| + [_panels insertObject:view atIndex:index];
|
| + [self updateScrollViewContent];
|
| +}
|
| +
|
| +- (void)removePanelViewAtIndex:(NSUInteger)index {
|
| + [self removePanelViewAtIndex:index updateScrollView:YES];
|
| +}
|
| +
|
| +- (void)removePanelViewAtIndex:(NSUInteger)index updateScrollView:(BOOL)update {
|
| + DCHECK_EQ([[_panels objectAtIndex:index] superview], _scrollView.get());
|
| + [[_panels objectAtIndex:index] removeFromSuperview];
|
| + [_panels removeObjectAtIndex:index];
|
| + if (update)
|
| + [self updateScrollViewContent];
|
| +}
|
| +
|
| +- (NSInteger)currentPanelIndex {
|
| + return [self currentPageIndex];
|
| +}
|
| +
|
| +- (void)updateOverlayButtonState {
|
| + NSInteger panelIndex = [self currentPageIndex];
|
| + ios_internal::NewTabButtonStyle newButtonStyle =
|
| + [delegate_ buttonStyleForPanelAtIndex:panelIndex];
|
| + [self setNewTabButtonStyle:newButtonStyle];
|
| + BOOL dismissButtonVisible =
|
| + [self.delegate shouldShowDismissButtonForPanelAtIndex:panelIndex];
|
| + [UIView beginAnimations:nil context:NULL];
|
| + [[_headerView dismissButton] setAlpha:dismissButtonVisible ? 1.0 : 0.0];
|
| + [UIView commitAnimations];
|
| +}
|
| +
|
| +#pragma mark - Private
|
| +
|
| +- (void)selectPanelAtIndex:(NSInteger)index animated:(BOOL)animated {
|
| + CGPoint pageOffset = CGPointZero;
|
| + pageOffset.x = self.bounds.size.width * index;
|
| + [_scrollView setContentOffset:pageOffset animated:animated];
|
| + [_headerView selectItemAtIndex:index];
|
| + UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification,
|
| + nil);
|
| +}
|
| +
|
| +- (NSInteger)currentPageIndex {
|
| + if ([_scrollView frame].size.width == 0)
|
| + return 0;
|
| + NSInteger index = floor(
|
| + [_scrollView contentOffset].x / [_scrollView frame].size.width + 0.5);
|
| + return std::max<NSInteger>(std::min<NSInteger>(index, [_panels count] - 1),
|
| + 0);
|
| +}
|
| +
|
| +- (void)updateHeaderSelection {
|
| + [_headerView selectItemAtIndex:[self currentPageIndex]];
|
| +}
|
| +
|
| +- (CGRect)frameForPanelAtIndex:(NSInteger)index {
|
| + CGSize panelSize = [_scrollView frame].size;
|
| + CGRect panelFrame =
|
| + CGRectMake(panelSize.width * index, 0, panelSize.width, panelSize.height);
|
| + return panelFrame;
|
| +}
|
| +
|
| +- (void)updateScrollViewContent {
|
| + CGSize panelSize = [_scrollView frame].size;
|
| + CGSize currentContentSize = [_scrollView contentSize];
|
| + currentContentSize.width = [_panels count] * panelSize.width;
|
| + [_scrollView setContentSize:currentContentSize];
|
| + for (NSUInteger i = 0; i < [_panels count]; i++) {
|
| + id panelView = [_panels objectAtIndex:i];
|
| + [panelView setFrame:[self frameForPanelAtIndex:i]];
|
| + }
|
| +}
|
| +
|
| +- (void)loadSubviews {
|
| + // Creates and add the header view showing the list of panels.
|
| + base::scoped_nsobject<TabSwitcherHeaderView> headerView(
|
| + [[TabSwitcherHeaderView alloc] initWithFrame:[self headerViewFrame]]);
|
| + [self addSubview:headerView];
|
| + _headerView = headerView;
|
| +
|
| + // Creates and add the scrollview containing the panels.
|
| + base::scoped_nsobject<UIScrollView> scrollView(
|
| + [[UIScrollView alloc] initWithFrame:[self scrollViewFrame]]);
|
| + [scrollView setBackgroundColor:[[MDCPalette greyPalette] tint900]];
|
| + [scrollView setAlwaysBounceHorizontal:YES];
|
| + [scrollView setDelegate:self];
|
| + [scrollView setPagingEnabled:YES];
|
| + [scrollView setDelegate:self];
|
| + if (UseRTLLayout())
|
| + [scrollView setTransform:CGAffineTransformMakeScale(-1, 1)];
|
| + [self addSubview:scrollView];
|
| + _scrollView = scrollView;
|
| +
|
| + // Creates and add the floating new tab button.
|
| + _openNewTabButton.reset([[MDCFloatingButton alloc] init]);
|
| + UIImage* openNewTabButtonImage =
|
| + [[UIImage imageNamed:@"tabswitcher_new_tab_fab"]
|
| + imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
| + [_openNewTabButton setImage:openNewTabButtonImage
|
| + forState:UIControlStateNormal];
|
| + [[_openNewTabButton imageView] setTintColor:[UIColor whiteColor]];
|
| + [_openNewTabButton setFrame:[self openNewTabButtonFrame]];
|
| + // When the button is positioned with autolayout, the animation of the
|
| + // button's position conflicts with other animations.
|
| + [_openNewTabButton
|
| + setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
|
| + UIViewAutoresizingFlexibleLeadingMargin()];
|
| + [_openNewTabButton addTarget:self
|
| + action:@selector(openNewTabButtonPressed)
|
| + forControlEvents:UIControlEventTouchUpInside];
|
| + [self addSubview:_openNewTabButton];
|
| + [self setNewTabButtonStyle:ios_internal::NewTabButtonStyle::GRAY];
|
| +}
|
| +
|
| +- (CGRect)headerViewFrame {
|
| + const CGFloat kStatusBarHeight =
|
| + [[UIApplication sharedApplication] statusBarFrame].size.height;
|
| + CGRect headerViewFrame = self.bounds;
|
| + headerViewFrame.origin.y += kStatusBarHeight;
|
| + headerViewFrame.size.height = kHeaderHeight;
|
| + return headerViewFrame;
|
| +}
|
| +
|
| +- (CGRect)scrollViewFrame {
|
| + CGRect scrollViewFrame = self.bounds;
|
| + scrollViewFrame.origin.y = CGRectGetMaxY([self headerViewFrame]);
|
| + scrollViewFrame.size.height -= scrollViewFrame.origin.y;
|
| + return scrollViewFrame;
|
| +}
|
| +
|
| +- (CGRect)openNewTabButtonFrame {
|
| + CGRect buttonFrame = self.bounds;
|
| + buttonFrame.origin.y = self.bounds.size.height -
|
| + kNewTabButtonMarginFromEdges - kNewTabButtonWidth;
|
| + if (UseRTLLayout()) {
|
| + buttonFrame.origin.x = kNewTabButtonMarginFromEdges;
|
| + } else {
|
| + buttonFrame.origin.x = self.bounds.size.width -
|
| + kNewTabButtonMarginFromEdges - kNewTabButtonWidth;
|
| + }
|
| + buttonFrame.size.width = kNewTabButtonWidth;
|
| + buttonFrame.size.height = kNewTabButtonWidth;
|
| + return buttonFrame;
|
| +}
|
| +
|
| +- (CGRect)openNewTabButtonFrameOffscreen {
|
| + CGRect buttonFrame = [self openNewTabButtonFrame];
|
| + buttonFrame.origin.y = self.bounds.size.height + kNewTabButtonWidth;
|
| + return buttonFrame;
|
| +}
|
| +
|
| +- (void)openNewTabButtonPressed {
|
| + [self.delegate openNewTabInPanelAtIndex:[self currentPageIndex]];
|
| +}
|
| +
|
| +- (void)setNewTabButtonStyle:(ios_internal::NewTabButtonStyle)newStyle {
|
| + if (newStyle == _openNewTabButtonStyle)
|
| + return;
|
| + _openNewTabButtonStyle = newStyle;
|
| + [UIView animateWithDuration:0.25
|
| + animations:^{
|
| + switch (_openNewTabButtonStyle) {
|
| + case ios_internal::NewTabButtonStyle::HIDDEN:
|
| + [_openNewTabButton setFrame:[self openNewTabButtonFrameOffscreen]];
|
| + break;
|
| + case ios_internal::NewTabButtonStyle::BLUE: {
|
| + [_openNewTabButton setFrame:[self openNewTabButtonFrame]];
|
| + MDCPalette* palette = [MDCPalette cr_bluePalette];
|
| + [_openNewTabButton
|
| + setInkColor:[[palette tint300] colorWithAlphaComponent:0.5f]];
|
| + [_openNewTabButton setBackgroundColor:[palette tint500]
|
| + forState:UIControlStateNormal];
|
| + [_openNewTabButton
|
| + setBackgroundColor:[UIColor colorWithWhite:0.8f alpha:1.0f]
|
| + forState:UIControlStateDisabled];
|
| + [_openNewTabButton
|
| + setAccessibilityLabel:l10n_util::GetNSString(
|
| + IDS_IOS_TAB_SWITCHER_CREATE_NEW_TAB)];
|
| + break;
|
| + }
|
| + case ios_internal::NewTabButtonStyle::GRAY: {
|
| + [_openNewTabButton setFrame:[self openNewTabButtonFrame]];
|
| + MDCPalette* palette = [MDCPalette greyPalette];
|
| + [_openNewTabButton
|
| + setInkColor:[[palette tint300] colorWithAlphaComponent:0.25f]];
|
| + [_openNewTabButton setBackgroundColor:[palette tint500]
|
| + forState:UIControlStateNormal];
|
| + [_openNewTabButton
|
| + setBackgroundColor:[UIColor colorWithWhite:0.8f alpha:1.0f]
|
| + forState:UIControlStateDisabled];
|
| + [_openNewTabButton
|
| + setAccessibilityLabel:
|
| + l10n_util::GetNSString(
|
| + IDS_IOS_TAB_SWITCHER_CREATE_NEW_INCOGNITO_TAB)];
|
| + break;
|
| + }
|
| + case ios_internal::NewTabButtonStyle::UNINITIALIZED:
|
| + NOTREACHED();
|
| + }
|
| + }
|
| + completion:^(BOOL finished) {
|
| + // Informs VoiceOver that the |_openNewTabButton| visibility may have
|
| + // changed.
|
| + UIAccessibilityPostNotification(
|
| + UIAccessibilityLayoutChangedNotification, nil);
|
| + }];
|
| +}
|
| +
|
| +#pragma mark - UIScrollViewDelegate
|
| +
|
| +- (void)scrollViewDidEndScrollingAnimation:(UIScrollView*)scrollView {
|
| + [self updateHeaderSelection];
|
| +}
|
| +
|
| +- (void)scrollViewDidScroll:(UIScrollView*)scrollView {
|
| + if ([scrollView isDragging]) {
|
| + [self updateHeaderSelection];
|
| + }
|
| + [self updateOverlayButtonState];
|
| +
|
| + NSInteger panelIndex = [self currentPanelIndex];
|
| + TabSwitcherPanelOverlayView* overlayView =
|
| + base::mac::ObjCCast<TabSwitcherPanelOverlayView>(
|
| + [_panels objectAtIndex:panelIndex]);
|
| + if (panelIndex != _previousPanelIndex && overlayView &&
|
| + [overlayView overlayType] ==
|
| + TabSwitcherPanelOverlayType::OVERLAY_PANEL_USER_SIGNED_OUT) {
|
| + base::RecordAction(
|
| + base::UserMetricsAction("Signin_Impression_FromTabSwitcher"));
|
| + }
|
| + _previousPanelIndex = panelIndex;
|
| +}
|
| +
|
| +#pragma mark - UIScrollViewAccessibilityDelegate
|
| +
|
| +- (NSString*)accessibilityScrollStatusForScrollView:(UIScrollView*)scrollView {
|
| + NSInteger panelIndex = [self currentPageIndex];
|
| + return [_headerView panelTitleAtIndex:panelIndex];
|
| +}
|
| +
|
| +#pragma mark - UIAccessibilityAction
|
| +
|
| +- (BOOL)accessibilityPerformEscape {
|
| + // If the Dismiss Button is visible, then the escape gesture triggers the
|
| + // dismissal of the tab switcher.
|
| + NSInteger panelIndex = [self currentPageIndex];
|
| + if ([self.delegate shouldShowDismissButtonForPanelAtIndex:panelIndex]) {
|
| + [delegate_ tabSwitcherViewDelegateDismissTabSwitcher:self];
|
| + return YES;
|
| + }
|
| + return NO;
|
| +}
|
| +
|
| +- (BOOL)accessibilityPerformMagicTap {
|
| + // If the New Tab Button is visible, then the magic tap opens a new tab.
|
| + NSInteger panelIndex = [self currentPageIndex];
|
| + ios_internal::NewTabButtonStyle buttonStyle =
|
| + [delegate_ buttonStyleForPanelAtIndex:panelIndex];
|
| + switch (buttonStyle) {
|
| + case ios_internal::BLUE:
|
| + case ios_internal::GRAY:
|
| + [self.delegate openNewTabInPanelAtIndex:panelIndex];
|
| + return YES;
|
| + case ios_internal::UNINITIALIZED:
|
| + case ios_internal::HIDDEN:
|
| + return NO;
|
| + }
|
| +}
|
| +
|
| +@end
|
|
|