OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/browser/ui/tab_switcher/tab_switcher_view.h" |
| 6 |
| 7 #include "base/logging.h" |
| 8 #include "base/mac/foundation_util.h" |
| 9 #include "base/mac/scoped_nsobject.h" |
| 10 #include "base/metrics/user_metrics.h" |
| 11 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" |
| 12 #include "ios/chrome/browser/ui/rtl_geometry.h" |
| 13 #import "ios/chrome/browser/ui/tab_switcher/tab_switcher_header_view.h" |
| 14 #import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_controller.h" |
| 15 #import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_overlay_view.h" |
| 16 #import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_view.h" |
| 17 #include "ios/chrome/grit/ios_strings.h" |
| 18 #import "ios/third_party/material_components_ios/src/components/Buttons/src/Mate
rialButtons.h" |
| 19 #import "ios/third_party/material_components_ios/src/components/Palettes/src/Mat
erialPalettes.h" |
| 20 #include "ui/base/l10n/l10n_util.h" |
| 21 |
| 22 namespace { |
| 23 const CGFloat kHeaderHeight = 95; |
| 24 const CGFloat kNewTabButtonMarginFromEdges = 48; |
| 25 const CGFloat kNewTabButtonWidth = 48; |
| 26 } |
| 27 |
| 28 @interface TabSwitcherView ()<UIScrollViewDelegate> { |
| 29 base::scoped_nsobject<TabSwitcherHeaderView> _headerView; |
| 30 base::scoped_nsobject<UIScrollView> _scrollView; |
| 31 base::scoped_nsobject<MDCButton> _openNewTabButton; |
| 32 base::scoped_nsobject<NSMutableArray> _panels; |
| 33 ios_internal::NewTabButtonStyle _openNewTabButtonStyle; |
| 34 NSInteger _previousPanelIndex; |
| 35 } |
| 36 |
| 37 // Returns the header view frame. |
| 38 - (CGRect)headerViewFrame; |
| 39 // Returns the scrollview frame. |
| 40 - (CGRect)scrollViewFrame; |
| 41 // Returns the new tab button frame. |
| 42 - (CGRect)openNewTabButtonFrame; |
| 43 // Returns the new tab button frame when hidden. |
| 44 - (CGRect)openNewTabButtonFrameOffscreen; |
| 45 // Called when the new tab button is pressed. |
| 46 - (void)openNewTabButtonPressed; |
| 47 // Returns the index of the panel presented in |_scrollview|. When two panels |
| 48 // are visible, it returns the most visible one. |
| 49 - (NSInteger)currentPageIndex; |
| 50 // Select the panel at the given index, updating both the header and content. |
| 51 // The scrollview scroll will be |animated| if requested. |
| 52 - (void)selectPanelAtIndex:(NSInteger)index animated:(BOOL)animated; |
| 53 @end |
| 54 |
| 55 @implementation TabSwitcherView |
| 56 |
| 57 @synthesize delegate = delegate_; |
| 58 |
| 59 - (instancetype)initWithFrame:(CGRect)frame { |
| 60 self = [super initWithFrame:frame]; |
| 61 if (self) { |
| 62 _openNewTabButtonStyle = ios_internal::NewTabButtonStyle::UNINITIALIZED; |
| 63 [self loadSubviews]; |
| 64 _panels.reset([[NSMutableArray alloc] init]); |
| 65 _previousPanelIndex = -1; |
| 66 } |
| 67 return self; |
| 68 } |
| 69 |
| 70 - (TabSwitcherHeaderView*)headerView { |
| 71 return _headerView; |
| 72 } |
| 73 |
| 74 - (UIScrollView*)scrollView { |
| 75 return _scrollView; |
| 76 } |
| 77 |
| 78 - (void)layoutSubviews { |
| 79 [super layoutSubviews]; |
| 80 NSInteger pageIndexBeforeLayout = [self currentPageIndex]; |
| 81 [CATransaction begin]; |
| 82 [CATransaction setDisableActions:YES]; |
| 83 [_headerView setFrame:[self headerViewFrame]]; |
| 84 [_scrollView setFrame:[self scrollViewFrame]]; |
| 85 [CATransaction commit]; |
| 86 [self updateScrollViewContent]; |
| 87 [self selectPanelAtIndex:pageIndexBeforeLayout animated:NO]; |
| 88 } |
| 89 |
| 90 - (void)selectPanelAtIndex:(NSInteger)index { |
| 91 [self selectPanelAtIndex:index animated:!UIAccessibilityIsVoiceOverRunning()]; |
| 92 } |
| 93 |
| 94 - (void)addPanelView:(UIView*)view atIndex:(NSUInteger)index { |
| 95 if (UseRTLLayout()) |
| 96 [view setTransform:CGAffineTransformMakeScale(-1, 1)]; |
| 97 [_scrollView addSubview:view]; |
| 98 [_panels insertObject:view atIndex:index]; |
| 99 [self updateScrollViewContent]; |
| 100 } |
| 101 |
| 102 - (void)removePanelViewAtIndex:(NSUInteger)index { |
| 103 [self removePanelViewAtIndex:index updateScrollView:YES]; |
| 104 } |
| 105 |
| 106 - (void)removePanelViewAtIndex:(NSUInteger)index updateScrollView:(BOOL)update { |
| 107 DCHECK_EQ([[_panels objectAtIndex:index] superview], _scrollView.get()); |
| 108 [[_panels objectAtIndex:index] removeFromSuperview]; |
| 109 [_panels removeObjectAtIndex:index]; |
| 110 if (update) |
| 111 [self updateScrollViewContent]; |
| 112 } |
| 113 |
| 114 - (NSInteger)currentPanelIndex { |
| 115 return [self currentPageIndex]; |
| 116 } |
| 117 |
| 118 - (void)updateOverlayButtonState { |
| 119 NSInteger panelIndex = [self currentPageIndex]; |
| 120 ios_internal::NewTabButtonStyle newButtonStyle = |
| 121 [delegate_ buttonStyleForPanelAtIndex:panelIndex]; |
| 122 [self setNewTabButtonStyle:newButtonStyle]; |
| 123 BOOL dismissButtonVisible = |
| 124 [self.delegate shouldShowDismissButtonForPanelAtIndex:panelIndex]; |
| 125 [UIView beginAnimations:nil context:NULL]; |
| 126 [[_headerView dismissButton] setAlpha:dismissButtonVisible ? 1.0 : 0.0]; |
| 127 [UIView commitAnimations]; |
| 128 } |
| 129 |
| 130 #pragma mark - Private |
| 131 |
| 132 - (void)selectPanelAtIndex:(NSInteger)index animated:(BOOL)animated { |
| 133 CGPoint pageOffset = CGPointZero; |
| 134 pageOffset.x = self.bounds.size.width * index; |
| 135 [_scrollView setContentOffset:pageOffset animated:animated]; |
| 136 [_headerView selectItemAtIndex:index]; |
| 137 UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, |
| 138 nil); |
| 139 } |
| 140 |
| 141 - (NSInteger)currentPageIndex { |
| 142 if ([_scrollView frame].size.width == 0) |
| 143 return 0; |
| 144 NSInteger index = floor( |
| 145 [_scrollView contentOffset].x / [_scrollView frame].size.width + 0.5); |
| 146 return std::max<NSInteger>(std::min<NSInteger>(index, [_panels count] - 1), |
| 147 0); |
| 148 } |
| 149 |
| 150 - (void)updateHeaderSelection { |
| 151 [_headerView selectItemAtIndex:[self currentPageIndex]]; |
| 152 } |
| 153 |
| 154 - (CGRect)frameForPanelAtIndex:(NSInteger)index { |
| 155 CGSize panelSize = [_scrollView frame].size; |
| 156 CGRect panelFrame = |
| 157 CGRectMake(panelSize.width * index, 0, panelSize.width, panelSize.height); |
| 158 return panelFrame; |
| 159 } |
| 160 |
| 161 - (void)updateScrollViewContent { |
| 162 CGSize panelSize = [_scrollView frame].size; |
| 163 CGSize currentContentSize = [_scrollView contentSize]; |
| 164 currentContentSize.width = [_panels count] * panelSize.width; |
| 165 [_scrollView setContentSize:currentContentSize]; |
| 166 for (NSUInteger i = 0; i < [_panels count]; i++) { |
| 167 id panelView = [_panels objectAtIndex:i]; |
| 168 [panelView setFrame:[self frameForPanelAtIndex:i]]; |
| 169 } |
| 170 } |
| 171 |
| 172 - (void)loadSubviews { |
| 173 // Creates and add the header view showing the list of panels. |
| 174 base::scoped_nsobject<TabSwitcherHeaderView> headerView( |
| 175 [[TabSwitcherHeaderView alloc] initWithFrame:[self headerViewFrame]]); |
| 176 [self addSubview:headerView]; |
| 177 _headerView = headerView; |
| 178 |
| 179 // Creates and add the scrollview containing the panels. |
| 180 base::scoped_nsobject<UIScrollView> scrollView( |
| 181 [[UIScrollView alloc] initWithFrame:[self scrollViewFrame]]); |
| 182 [scrollView setBackgroundColor:[[MDCPalette greyPalette] tint900]]; |
| 183 [scrollView setAlwaysBounceHorizontal:YES]; |
| 184 [scrollView setDelegate:self]; |
| 185 [scrollView setPagingEnabled:YES]; |
| 186 [scrollView setDelegate:self]; |
| 187 if (UseRTLLayout()) |
| 188 [scrollView setTransform:CGAffineTransformMakeScale(-1, 1)]; |
| 189 [self addSubview:scrollView]; |
| 190 _scrollView = scrollView; |
| 191 |
| 192 // Creates and add the floating new tab button. |
| 193 _openNewTabButton.reset([[MDCFloatingButton alloc] init]); |
| 194 UIImage* openNewTabButtonImage = |
| 195 [[UIImage imageNamed:@"tabswitcher_new_tab_fab"] |
| 196 imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
| 197 [_openNewTabButton setImage:openNewTabButtonImage |
| 198 forState:UIControlStateNormal]; |
| 199 [[_openNewTabButton imageView] setTintColor:[UIColor whiteColor]]; |
| 200 [_openNewTabButton setFrame:[self openNewTabButtonFrame]]; |
| 201 // When the button is positioned with autolayout, the animation of the |
| 202 // button's position conflicts with other animations. |
| 203 [_openNewTabButton |
| 204 setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | |
| 205 UIViewAutoresizingFlexibleLeadingMargin()]; |
| 206 [_openNewTabButton addTarget:self |
| 207 action:@selector(openNewTabButtonPressed) |
| 208 forControlEvents:UIControlEventTouchUpInside]; |
| 209 [self addSubview:_openNewTabButton]; |
| 210 [self setNewTabButtonStyle:ios_internal::NewTabButtonStyle::GRAY]; |
| 211 } |
| 212 |
| 213 - (CGRect)headerViewFrame { |
| 214 const CGFloat kStatusBarHeight = |
| 215 [[UIApplication sharedApplication] statusBarFrame].size.height; |
| 216 CGRect headerViewFrame = self.bounds; |
| 217 headerViewFrame.origin.y += kStatusBarHeight; |
| 218 headerViewFrame.size.height = kHeaderHeight; |
| 219 return headerViewFrame; |
| 220 } |
| 221 |
| 222 - (CGRect)scrollViewFrame { |
| 223 CGRect scrollViewFrame = self.bounds; |
| 224 scrollViewFrame.origin.y = CGRectGetMaxY([self headerViewFrame]); |
| 225 scrollViewFrame.size.height -= scrollViewFrame.origin.y; |
| 226 return scrollViewFrame; |
| 227 } |
| 228 |
| 229 - (CGRect)openNewTabButtonFrame { |
| 230 CGRect buttonFrame = self.bounds; |
| 231 buttonFrame.origin.y = self.bounds.size.height - |
| 232 kNewTabButtonMarginFromEdges - kNewTabButtonWidth; |
| 233 if (UseRTLLayout()) { |
| 234 buttonFrame.origin.x = kNewTabButtonMarginFromEdges; |
| 235 } else { |
| 236 buttonFrame.origin.x = self.bounds.size.width - |
| 237 kNewTabButtonMarginFromEdges - kNewTabButtonWidth; |
| 238 } |
| 239 buttonFrame.size.width = kNewTabButtonWidth; |
| 240 buttonFrame.size.height = kNewTabButtonWidth; |
| 241 return buttonFrame; |
| 242 } |
| 243 |
| 244 - (CGRect)openNewTabButtonFrameOffscreen { |
| 245 CGRect buttonFrame = [self openNewTabButtonFrame]; |
| 246 buttonFrame.origin.y = self.bounds.size.height + kNewTabButtonWidth; |
| 247 return buttonFrame; |
| 248 } |
| 249 |
| 250 - (void)openNewTabButtonPressed { |
| 251 [self.delegate openNewTabInPanelAtIndex:[self currentPageIndex]]; |
| 252 } |
| 253 |
| 254 - (void)setNewTabButtonStyle:(ios_internal::NewTabButtonStyle)newStyle { |
| 255 if (newStyle == _openNewTabButtonStyle) |
| 256 return; |
| 257 _openNewTabButtonStyle = newStyle; |
| 258 [UIView animateWithDuration:0.25 |
| 259 animations:^{ |
| 260 switch (_openNewTabButtonStyle) { |
| 261 case ios_internal::NewTabButtonStyle::HIDDEN: |
| 262 [_openNewTabButton setFrame:[self openNewTabButtonFrameOffscreen]]; |
| 263 break; |
| 264 case ios_internal::NewTabButtonStyle::BLUE: { |
| 265 [_openNewTabButton setFrame:[self openNewTabButtonFrame]]; |
| 266 MDCPalette* palette = [MDCPalette cr_bluePalette]; |
| 267 [_openNewTabButton |
| 268 setInkColor:[[palette tint300] colorWithAlphaComponent:0.5f]]; |
| 269 [_openNewTabButton setBackgroundColor:[palette tint500] |
| 270 forState:UIControlStateNormal]; |
| 271 [_openNewTabButton |
| 272 setBackgroundColor:[UIColor colorWithWhite:0.8f alpha:1.0f] |
| 273 forState:UIControlStateDisabled]; |
| 274 [_openNewTabButton |
| 275 setAccessibilityLabel:l10n_util::GetNSString( |
| 276 IDS_IOS_TAB_SWITCHER_CREATE_NEW_TAB)]; |
| 277 break; |
| 278 } |
| 279 case ios_internal::NewTabButtonStyle::GRAY: { |
| 280 [_openNewTabButton setFrame:[self openNewTabButtonFrame]]; |
| 281 MDCPalette* palette = [MDCPalette greyPalette]; |
| 282 [_openNewTabButton |
| 283 setInkColor:[[palette tint300] colorWithAlphaComponent:0.25f]]; |
| 284 [_openNewTabButton setBackgroundColor:[palette tint500] |
| 285 forState:UIControlStateNormal]; |
| 286 [_openNewTabButton |
| 287 setBackgroundColor:[UIColor colorWithWhite:0.8f alpha:1.0f] |
| 288 forState:UIControlStateDisabled]; |
| 289 [_openNewTabButton |
| 290 setAccessibilityLabel: |
| 291 l10n_util::GetNSString( |
| 292 IDS_IOS_TAB_SWITCHER_CREATE_NEW_INCOGNITO_TAB)]; |
| 293 break; |
| 294 } |
| 295 case ios_internal::NewTabButtonStyle::UNINITIALIZED: |
| 296 NOTREACHED(); |
| 297 } |
| 298 } |
| 299 completion:^(BOOL finished) { |
| 300 // Informs VoiceOver that the |_openNewTabButton| visibility may have |
| 301 // changed. |
| 302 UIAccessibilityPostNotification( |
| 303 UIAccessibilityLayoutChangedNotification, nil); |
| 304 }]; |
| 305 } |
| 306 |
| 307 #pragma mark - UIScrollViewDelegate |
| 308 |
| 309 - (void)scrollViewDidEndScrollingAnimation:(UIScrollView*)scrollView { |
| 310 [self updateHeaderSelection]; |
| 311 } |
| 312 |
| 313 - (void)scrollViewDidScroll:(UIScrollView*)scrollView { |
| 314 if ([scrollView isDragging]) { |
| 315 [self updateHeaderSelection]; |
| 316 } |
| 317 [self updateOverlayButtonState]; |
| 318 |
| 319 NSInteger panelIndex = [self currentPanelIndex]; |
| 320 TabSwitcherPanelOverlayView* overlayView = |
| 321 base::mac::ObjCCast<TabSwitcherPanelOverlayView>( |
| 322 [_panels objectAtIndex:panelIndex]); |
| 323 if (panelIndex != _previousPanelIndex && overlayView && |
| 324 [overlayView overlayType] == |
| 325 TabSwitcherPanelOverlayType::OVERLAY_PANEL_USER_SIGNED_OUT) { |
| 326 base::RecordAction( |
| 327 base::UserMetricsAction("Signin_Impression_FromTabSwitcher")); |
| 328 } |
| 329 _previousPanelIndex = panelIndex; |
| 330 } |
| 331 |
| 332 #pragma mark - UIScrollViewAccessibilityDelegate |
| 333 |
| 334 - (NSString*)accessibilityScrollStatusForScrollView:(UIScrollView*)scrollView { |
| 335 NSInteger panelIndex = [self currentPageIndex]; |
| 336 return [_headerView panelTitleAtIndex:panelIndex]; |
| 337 } |
| 338 |
| 339 #pragma mark - UIAccessibilityAction |
| 340 |
| 341 - (BOOL)accessibilityPerformEscape { |
| 342 // If the Dismiss Button is visible, then the escape gesture triggers the |
| 343 // dismissal of the tab switcher. |
| 344 NSInteger panelIndex = [self currentPageIndex]; |
| 345 if ([self.delegate shouldShowDismissButtonForPanelAtIndex:panelIndex]) { |
| 346 [delegate_ tabSwitcherViewDelegateDismissTabSwitcher:self]; |
| 347 return YES; |
| 348 } |
| 349 return NO; |
| 350 } |
| 351 |
| 352 - (BOOL)accessibilityPerformMagicTap { |
| 353 // If the New Tab Button is visible, then the magic tap opens a new tab. |
| 354 NSInteger panelIndex = [self currentPageIndex]; |
| 355 ios_internal::NewTabButtonStyle buttonStyle = |
| 356 [delegate_ buttonStyleForPanelAtIndex:panelIndex]; |
| 357 switch (buttonStyle) { |
| 358 case ios_internal::BLUE: |
| 359 case ios_internal::GRAY: |
| 360 [self.delegate openNewTabInPanelAtIndex:panelIndex]; |
| 361 return YES; |
| 362 case ios_internal::UNINITIALIZED: |
| 363 case ios_internal::HIDDEN: |
| 364 return NO; |
| 365 } |
| 366 } |
| 367 |
| 368 @end |
OLD | NEW |