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/side_swipe/side_swipe_controller.h" |
| 6 |
| 7 #include <memory> |
| 8 |
| 9 #include "components/reading_list/core/reading_list_switches.h" |
| 10 #import "components/reading_list/ios/reading_list_model.h" |
| 11 #import "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| 12 #import "ios/chrome/browser/infobars/infobar_container_view.h" |
| 13 #import "ios/chrome/browser/reading_list/reading_list_model_factory.h" |
| 14 #import "ios/chrome/browser/snapshots/snapshot_cache.h" |
| 15 #import "ios/chrome/browser/tabs/tab.h" |
| 16 #import "ios/chrome/browser/tabs/tab_model_observer.h" |
| 17 #import "ios/chrome/browser/ui/reading_list/reading_list_side_swipe_provider.h" |
| 18 #import "ios/chrome/browser/ui/side_swipe/card_side_swipe_view.h" |
| 19 #import "ios/chrome/browser/ui/side_swipe/history_side_swipe_provider.h" |
| 20 #import "ios/chrome/browser/ui/side_swipe/side_swipe_navigation_view.h" |
| 21 #import "ios/chrome/browser/ui/side_swipe/side_swipe_util.h" |
| 22 #import "ios/chrome/browser/ui/side_swipe_gesture_recognizer.h" |
| 23 #include "ios/chrome/browser/ui/ui_util.h" |
| 24 #import "ios/web/public/web_state/web_state_observer_bridge.h" |
| 25 #import "ios/web/web_state/ui/crw_web_controller.h" |
| 26 |
| 27 namespace ios_internal { |
| 28 NSString* const kSideSwipeWillStartNotification = |
| 29 @"kSideSwipeWillStartNotification"; |
| 30 NSString* const kSideSwipeDidStopNotification = |
| 31 @"kSideSwipeDidStopNotification"; |
| 32 } // namespace ios_internal |
| 33 |
| 34 namespace { |
| 35 |
| 36 enum class SwipeType { NONE, CHANGE_TAB, CHANGE_PAGE }; |
| 37 |
| 38 // Swipe starting distance from edge. |
| 39 const CGFloat kSwipeEdge = 20; |
| 40 |
| 41 // Distance between sections of iPad side swipe. |
| 42 const CGFloat kIpadTabSwipeDistance = 100; |
| 43 |
| 44 // Number of tabs to keep in the grey image cache. |
| 45 const NSUInteger kIpadGreySwipeTabCount = 8; |
| 46 } |
| 47 |
| 48 @interface SideSwipeController ()<CRWWebStateObserver, |
| 49 TabModelObserver, |
| 50 UIGestureRecognizerDelegate> { |
| 51 @private |
| 52 |
| 53 TabModel* model_; |
| 54 |
| 55 // Side swipe view for tab navigation. |
| 56 base::scoped_nsobject<CardSideSwipeView> tabSideSwipeView_; |
| 57 |
| 58 // Side swipe view for page navigation. |
| 59 base::scoped_nsobject<SideSwipeNavigationView> pageSideSwipeView_; |
| 60 |
| 61 // YES if the user is currently swiping. |
| 62 BOOL inSwipe_; |
| 63 |
| 64 // Swipe gesture recognizer. |
| 65 base::scoped_nsobject<SideSwipeGestureRecognizer> swipeGestureRecognizer_; |
| 66 |
| 67 base::scoped_nsobject<SideSwipeGestureRecognizer> panGestureRecognizer_; |
| 68 |
| 69 // Used in iPad side swipe gesture, tracks the starting tab index. |
| 70 NSUInteger startingTabIndex_; |
| 71 |
| 72 // If the swipe is for a page change or a tab change. |
| 73 SwipeType swipeType_; |
| 74 |
| 75 // Bridge to observe the web state from Objective-C. |
| 76 std::unique_ptr<web::WebStateObserverBridge> webStateObserverBridge_; |
| 77 |
| 78 // Curtain over web view while waiting for it to load. |
| 79 base::scoped_nsobject<UIView> curtain_; |
| 80 |
| 81 // Provides forward/back action for history entries. |
| 82 base::scoped_nsobject<HistorySideSwipeProvider> historySideSwipeProvider_; |
| 83 |
| 84 // Provides forward action for reading list. |
| 85 base::scoped_nsobject<ReadingListSideSwipeProvider> |
| 86 readingListSideSwipeProvider_; |
| 87 |
| 88 base::WeakNSProtocol<id<SideSwipeContentProvider>> currentContentProvider_; |
| 89 } |
| 90 |
| 91 // Load grey snapshots for the next |kIpadGreySwipeTabCount| tabs in |
| 92 // |direction|. |
| 93 - (void)createGreyCache:(UISwipeGestureRecognizerDirection)direction; |
| 94 // Tell snapshot cache to clear grey cache. |
| 95 - (void)deleteGreyCache; |
| 96 // Handle tab side swipe for iPad. Change tabs according to swipe distance. |
| 97 - (void)handleiPadTabSwipe:(SideSwipeGestureRecognizer*)gesture; |
| 98 // Handle tab side swipe for iPhone. Introduces a CardSideSwipeView to convey |
| 99 // the tab change. |
| 100 - (void)handleiPhoneTabSwipe:(SideSwipeGestureRecognizer*)gesture; |
| 101 // Overlays |curtain_| as a white view to hide the web view while it updates. |
| 102 // Calls |completionHandler| when the curtain is removed. |
| 103 - (void)addCurtainWithCompletionHandler:(ProceduralBlock)completionHandler; |
| 104 // Removes the |curtain_| and calls |completionHandler| when the curtain is |
| 105 // removed. |
| 106 - (void)dismissCurtainWithCompletionHandler:(ProceduralBlock)completionHandler; |
| 107 @end |
| 108 |
| 109 @implementation SideSwipeController |
| 110 |
| 111 @synthesize inSwipe = inSwipe_; |
| 112 @synthesize swipeDelegate = swipeDelegate_; |
| 113 @synthesize snapshotDelegate = snapshotDelegate_; |
| 114 |
| 115 - (id)initWithTabModel:(TabModel*)model |
| 116 browserState:(ios::ChromeBrowserState*)browserState { |
| 117 DCHECK(model); |
| 118 self = [super init]; |
| 119 if (self) { |
| 120 model_ = model; |
| 121 [model_ addObserver:self]; |
| 122 historySideSwipeProvider_.reset( |
| 123 [[HistorySideSwipeProvider alloc] initWithTabModel:model_]); |
| 124 |
| 125 if (!browserState->IsOffTheRecord() && |
| 126 reading_list::switches::IsReadingListEnabled()) { |
| 127 readingListSideSwipeProvider_.reset([[ReadingListSideSwipeProvider alloc] |
| 128 initWithReadingList:ReadingListModelFactory::GetForBrowserState( |
| 129 browserState)]); |
| 130 } |
| 131 } |
| 132 return self; |
| 133 } |
| 134 |
| 135 - (void)dealloc { |
| 136 [model_ removeObserver:self]; |
| 137 [super dealloc]; |
| 138 } |
| 139 |
| 140 - (void)addHorizontalGesturesToView:(UIView*)view { |
| 141 swipeGestureRecognizer_.reset([[SideSwipeGestureRecognizer alloc] |
| 142 initWithTarget:self |
| 143 action:@selector(handleSwipe:)]); |
| 144 [swipeGestureRecognizer_ setMaximumNumberOfTouches:1]; |
| 145 [swipeGestureRecognizer_ setDelegate:self]; |
| 146 [swipeGestureRecognizer_ setSwipeEdge:kSwipeEdge]; |
| 147 [view addGestureRecognizer:swipeGestureRecognizer_]; |
| 148 |
| 149 // Add a second gesture recognizer to handle swiping on the toolbar to change |
| 150 // tabs. |
| 151 panGestureRecognizer_.reset([[SideSwipeGestureRecognizer alloc] |
| 152 initWithTarget:self |
| 153 action:@selector(handlePan:)]); |
| 154 [panGestureRecognizer_ setMaximumNumberOfTouches:1]; |
| 155 [panGestureRecognizer_ setSwipeThreshold:48]; |
| 156 [panGestureRecognizer_ setDelegate:self]; |
| 157 [view addGestureRecognizer:panGestureRecognizer_]; |
| 158 } |
| 159 |
| 160 - (NSSet*)swipeRecognizers { |
| 161 return [NSSet setWithObjects:swipeGestureRecognizer_.get(), nil]; |
| 162 } |
| 163 |
| 164 - (void)setEnabled:(BOOL)enabled { |
| 165 [swipeGestureRecognizer_ setEnabled:enabled]; |
| 166 } |
| 167 |
| 168 - (BOOL)shouldAutorotate { |
| 169 return !([tabSideSwipeView_ window] || inSwipe_); |
| 170 } |
| 171 |
| 172 // Always return yes, as this swipe should work with various recognizers, |
| 173 // including UITextTapRecognizer, UILongPressGestureRecognizer, |
| 174 // UIScrollViewPanGestureRecognizer and others. |
| 175 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer |
| 176 shouldRecognizeSimultaneouslyWithGestureRecognizer: |
| 177 (UIGestureRecognizer*)otherGestureRecognizer { |
| 178 return YES; |
| 179 } |
| 180 |
| 181 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer |
| 182 shouldBeRequiredToFailByGestureRecognizer: |
| 183 (UIGestureRecognizer*)otherGestureRecognizer { |
| 184 // Only take precedence over a pan gesture recognizer so that moving up and |
| 185 // down while swiping doesn't trigger overscroll actions. |
| 186 if ([otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { |
| 187 return YES; |
| 188 } |
| 189 return NO; |
| 190 } |
| 191 |
| 192 // Gestures should only be recognized within |contentArea_| or the toolbar. |
| 193 - (BOOL)gestureRecognizerShouldBegin:(SideSwipeGestureRecognizer*)gesture { |
| 194 if (inSwipe_) { |
| 195 return NO; |
| 196 } |
| 197 |
| 198 if ([swipeDelegate_ preventSideSwipe]) |
| 199 return NO; |
| 200 |
| 201 CGPoint location = [gesture locationInView:gesture.view]; |
| 202 |
| 203 // Both the toolbar frame and the contentView frame below are inset by |
| 204 // -1 because CGRectContainsPoint does include points on the max X and Y |
| 205 // edges, which will happen frequently with edge swipes from the right side. |
| 206 // Since the toolbar and the contentView can overlap, check the toolbar frame |
| 207 // first, and confirm the right gesture recognizer is firing. |
| 208 CGRect toolbarFrame = |
| 209 CGRectInset([[[swipeDelegate_ toolbarController] view] frame], -1, -1); |
| 210 if (CGRectContainsPoint(toolbarFrame, location)) { |
| 211 if (![gesture isEqual:panGestureRecognizer_]) { |
| 212 return NO; |
| 213 } |
| 214 |
| 215 if ([[swipeDelegate_ toolbarController] isOmniboxFirstResponder] || |
| 216 [[swipeDelegate_ toolbarController] showingOmniboxPopup]) { |
| 217 return NO; |
| 218 } |
| 219 return YES; |
| 220 } |
| 221 |
| 222 // Otherwise, only allow contentView touches with |swipeGestureRecognizer_|. |
| 223 CGRect contentViewFrame = |
| 224 CGRectInset([[swipeDelegate_ contentView] frame], -1, -1); |
| 225 if (CGRectContainsPoint(contentViewFrame, location)) { |
| 226 if (![gesture isEqual:swipeGestureRecognizer_]) { |
| 227 return NO; |
| 228 } |
| 229 swipeType_ = SwipeType::CHANGE_PAGE; |
| 230 return YES; |
| 231 } |
| 232 return NO; |
| 233 } |
| 234 |
| 235 - (void)createGreyCache:(UISwipeGestureRecognizerDirection)direction { |
| 236 NSInteger dx = (direction == UISwipeGestureRecognizerDirectionLeft) ? -1 : 1; |
| 237 NSInteger index = startingTabIndex_ + dx; |
| 238 NSMutableArray* sessionIDs = |
| 239 [NSMutableArray arrayWithCapacity:kIpadGreySwipeTabCount]; |
| 240 for (NSUInteger count = 0; count < kIpadGreySwipeTabCount; count++) { |
| 241 // Wrap around edges. |
| 242 if (index >= (NSInteger)[model_ count]) |
| 243 index = 0; |
| 244 else if (index < 0) |
| 245 index = [model_ count] - 1; |
| 246 |
| 247 // Don't wrap past the starting index. |
| 248 if (index == (NSInteger)startingTabIndex_) |
| 249 break; |
| 250 |
| 251 Tab* tab = [model_ tabAtIndex:index]; |
| 252 if (tab && tab.webController.usePlaceholderOverlay) { |
| 253 [sessionIDs addObject:[tab currentSessionID]]; |
| 254 } |
| 255 index = index + dx; |
| 256 } |
| 257 [[SnapshotCache sharedInstance] createGreyCache:sessionIDs]; |
| 258 for (Tab* tab in model_) { |
| 259 tab.useGreyImageCache = YES; |
| 260 } |
| 261 } |
| 262 |
| 263 - (void)deleteGreyCache { |
| 264 [[SnapshotCache sharedInstance] removeGreyCache]; |
| 265 for (Tab* tab in model_) { |
| 266 tab.useGreyImageCache = NO; |
| 267 } |
| 268 } |
| 269 |
| 270 - (void)handlePan:(SideSwipeGestureRecognizer*)gesture { |
| 271 if (!IsIPadIdiom()) { |
| 272 return [self handleiPhoneTabSwipe:gesture]; |
| 273 } else { |
| 274 return [self handleiPadTabSwipe:gesture]; |
| 275 } |
| 276 } |
| 277 |
| 278 - (void)handleSwipe:(SideSwipeGestureRecognizer*)gesture { |
| 279 DCHECK(swipeType_ != SwipeType::NONE); |
| 280 if (swipeType_ == SwipeType::CHANGE_TAB) { |
| 281 if (!IsIPadIdiom()) { |
| 282 return [self handleiPhoneTabSwipe:gesture]; |
| 283 } else { |
| 284 return [self handleiPadTabSwipe:gesture]; |
| 285 } |
| 286 } |
| 287 if (swipeType_ == SwipeType::CHANGE_PAGE) { |
| 288 return [self handleSwipeToNavigate:gesture]; |
| 289 } |
| 290 NOTREACHED(); |
| 291 } |
| 292 |
| 293 - (void)handleiPadTabSwipe:(SideSwipeGestureRecognizer*)gesture { |
| 294 // Don't handle swipe when there are no tabs. |
| 295 NSInteger count = [model_ count]; |
| 296 if (count == 0) |
| 297 return; |
| 298 |
| 299 if (gesture.state == UIGestureRecognizerStateBegan) { |
| 300 // If the toolbar is hidden, move it to visible. |
| 301 [[model_ currentTab] updateFullscreenWithToolbarVisible:YES]; |
| 302 [[model_ currentTab] updateSnapshotWithOverlay:YES visibleFrameOnly:YES]; |
| 303 [[NSNotificationCenter defaultCenter] |
| 304 postNotificationName:ios_internal::kSideSwipeWillStartNotification |
| 305 object:nil]; |
| 306 [[swipeDelegate_ tabStripController] setHighlightsSelectedTab:YES]; |
| 307 startingTabIndex_ = [model_ indexOfTab:[model_ currentTab]]; |
| 308 [self createGreyCache:gesture.direction]; |
| 309 } else if (gesture.state == UIGestureRecognizerStateChanged) { |
| 310 // Side swipe for iPad involves changing the selected tab as the swipe moves |
| 311 // across the width of the view. The screen is broken up into |
| 312 // |kIpadTabSwipeDistance| / |width| segments, with the current tab in the |
| 313 // first section. The swipe does not wrap edges. |
| 314 CGFloat distance = [gesture locationInView:gesture.view].x; |
| 315 if (gesture.direction == UISwipeGestureRecognizerDirectionLeft) { |
| 316 distance = gesture.startPoint.x - distance; |
| 317 } else { |
| 318 distance -= gesture.startPoint.x; |
| 319 } |
| 320 |
| 321 NSInteger indexDelta = std::floor(distance / kIpadTabSwipeDistance); |
| 322 // Don't wrap past the first tab. |
| 323 if (indexDelta < count) { |
| 324 // Flip delta when swiping forward. |
| 325 if (IsSwipingForward(gesture.direction)) |
| 326 indexDelta = 0 - indexDelta; |
| 327 |
| 328 Tab* currentTab = [model_ currentTab]; |
| 329 NSInteger currentIndex = [model_ indexOfTab:currentTab]; |
| 330 |
| 331 // Wrap around edges. |
| 332 NSInteger newIndex = (NSInteger)(startingTabIndex_ + indexDelta) % count; |
| 333 |
| 334 // C99 defines the modulo result as negative if our offset is negative. |
| 335 if (newIndex < 0) |
| 336 newIndex += count; |
| 337 |
| 338 if (newIndex != currentIndex) { |
| 339 Tab* tab = [model_ tabAtIndex:newIndex]; |
| 340 // Toggle overlay preview mode for selected tab. |
| 341 [tab.webController setOverlayPreviewMode:YES]; |
| 342 [model_ setCurrentTab:tab]; |
| 343 // And disable overlay preview mode for last selected tab. |
| 344 [currentTab.webController setOverlayPreviewMode:NO]; |
| 345 } |
| 346 } |
| 347 } else { |
| 348 if (gesture.state == UIGestureRecognizerStateCancelled) { |
| 349 Tab* tab = [model_ tabAtIndex:startingTabIndex_]; |
| 350 [[model_ currentTab].webController setOverlayPreviewMode:NO]; |
| 351 [model_ setCurrentTab:tab]; |
| 352 } |
| 353 [[model_ currentTab].webController setOverlayPreviewMode:NO]; |
| 354 |
| 355 // Redisplay the view if it was in overlay preview mode. |
| 356 [swipeDelegate_ displayTab:[model_ currentTab] isNewSelection:YES]; |
| 357 [[swipeDelegate_ tabStripController] setHighlightsSelectedTab:NO]; |
| 358 [self deleteGreyCache]; |
| 359 [[NSNotificationCenter defaultCenter] |
| 360 postNotificationName:ios_internal::kSideSwipeDidStopNotification |
| 361 object:nil]; |
| 362 } |
| 363 } |
| 364 |
| 365 - (id<SideSwipeContentProvider>)contentProviderForGesture:(BOOL)goBack { |
| 366 if (goBack && [historySideSwipeProvider_ canGoBack]) { |
| 367 return historySideSwipeProvider_; |
| 368 } |
| 369 if (!goBack && [historySideSwipeProvider_ canGoForward]) { |
| 370 return historySideSwipeProvider_; |
| 371 } |
| 372 if (goBack && [readingListSideSwipeProvider_ canGoBack]) { |
| 373 return readingListSideSwipeProvider_; |
| 374 } |
| 375 if (!goBack && [readingListSideSwipeProvider_ canGoForward]) { |
| 376 return readingListSideSwipeProvider_; |
| 377 } |
| 378 return nil; |
| 379 } |
| 380 |
| 381 // Show swipe to navigate. |
| 382 - (void)handleSwipeToNavigate:(SideSwipeGestureRecognizer*)gesture { |
| 383 if (gesture.state == UIGestureRecognizerStateBegan) { |
| 384 // If the toolbar is hidden, move it to visible. |
| 385 [[model_ currentTab] updateFullscreenWithToolbarVisible:YES]; |
| 386 |
| 387 inSwipe_ = YES; |
| 388 [swipeDelegate_ updateAccessoryViewsForSideSwipeWithVisibility:NO]; |
| 389 BOOL goBack = IsSwipingBack(gesture.direction); |
| 390 |
| 391 currentContentProvider_.reset([self contentProviderForGesture:goBack]); |
| 392 BOOL canNavigate = currentContentProvider_ != nil; |
| 393 |
| 394 CGRect gestureBounds = gesture.view.bounds; |
| 395 CGFloat headerHeight = [swipeDelegate_ headerHeight]; |
| 396 CGRect navigationFrame = |
| 397 CGRectMake(CGRectGetMinX(gestureBounds), |
| 398 CGRectGetMinY(gestureBounds) + headerHeight, |
| 399 CGRectGetWidth(gestureBounds), |
| 400 CGRectGetHeight(gestureBounds) - headerHeight); |
| 401 |
| 402 pageSideSwipeView_.reset([[SideSwipeNavigationView alloc] |
| 403 initWithFrame:navigationFrame |
| 404 withDirection:gesture.direction |
| 405 canNavigate:canNavigate |
| 406 image:[currentContentProvider_ paneIcon] |
| 407 rotateForward:[currentContentProvider_ rotateForwardIcon]]); |
| 408 [pageSideSwipeView_ setTargetView:[swipeDelegate_ contentView]]; |
| 409 |
| 410 [gesture.view insertSubview:pageSideSwipeView_ |
| 411 belowSubview:[[swipeDelegate_ toolbarController] view]]; |
| 412 } |
| 413 |
| 414 base::WeakNSObject<Tab> weakCurrentTab([model_ currentTab]); |
| 415 [pageSideSwipeView_ handleHorizontalPan:gesture |
| 416 onOverThresholdCompletion:^{ |
| 417 BOOL wantsBack = IsSwipingBack(gesture.direction); |
| 418 web::WebState* webState = [weakCurrentTab webState]; |
| 419 if (wantsBack) { |
| 420 [currentContentProvider_ goBack:webState]; |
| 421 } else { |
| 422 [currentContentProvider_ goForward:webState]; |
| 423 } |
| 424 |
| 425 if (webState && webState->IsLoading()) { |
| 426 webStateObserverBridge_.reset( |
| 427 new web::WebStateObserverBridge(webState, self)); |
| 428 [self addCurtainWithCompletionHandler:^{ |
| 429 inSwipe_ = NO; |
| 430 }]; |
| 431 } else { |
| 432 inSwipe_ = NO; |
| 433 } |
| 434 [swipeDelegate_ updateAccessoryViewsForSideSwipeWithVisibility:YES]; |
| 435 } |
| 436 onUnderThresholdCompletion:^{ |
| 437 [swipeDelegate_ updateAccessoryViewsForSideSwipeWithVisibility:YES]; |
| 438 inSwipe_ = NO; |
| 439 }]; |
| 440 } |
| 441 |
| 442 // Show horizontal swipe stack view for iPhone. |
| 443 - (void)handleiPhoneTabSwipe:(SideSwipeGestureRecognizer*)gesture { |
| 444 if (gesture.state == UIGestureRecognizerStateBegan) { |
| 445 // If the toolbar is hidden, move it to visible. |
| 446 [[model_ currentTab] updateFullscreenWithToolbarVisible:YES]; |
| 447 |
| 448 inSwipe_ = YES; |
| 449 |
| 450 CGRect frame = [[swipeDelegate_ contentView] frame]; |
| 451 |
| 452 // Add horizontal stack view controller. |
| 453 CGFloat headerHeight = |
| 454 [self.snapshotDelegate snapshotContentAreaForTab:[model_ currentTab]] |
| 455 .origin.y; |
| 456 if (tabSideSwipeView_) { |
| 457 [tabSideSwipeView_ setFrame:frame]; |
| 458 [tabSideSwipeView_ setTopMargin:headerHeight]; |
| 459 } else { |
| 460 tabSideSwipeView_.reset([[CardSideSwipeView alloc] |
| 461 initWithFrame:frame |
| 462 topMargin:headerHeight |
| 463 model:model_]); |
| 464 [tabSideSwipeView_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | |
| 465 UIViewAutoresizingFlexibleHeight]; |
| 466 [tabSideSwipeView_ setDelegate:swipeDelegate_]; |
| 467 [tabSideSwipeView_ setBackgroundColor:[UIColor blackColor]]; |
| 468 } |
| 469 |
| 470 // Ensure that there's an up-to-date snapshot of the current tab. |
| 471 [[model_ currentTab] updateSnapshotWithOverlay:YES visibleFrameOnly:YES]; |
| 472 // Hide the infobar after snapshot has been updated (see the previous line) |
| 473 // to avoid it obscuring the cards in the side swipe view. |
| 474 [swipeDelegate_ updateAccessoryViewsForSideSwipeWithVisibility:NO]; |
| 475 |
| 476 // Layout tabs with new snapshots in the current orientation. |
| 477 [tabSideSwipeView_ |
| 478 updateViewsForDirection:gesture.direction |
| 479 withToolbar:[swipeDelegate_ toolbarController]]; |
| 480 |
| 481 // Insert behind infobar container (which is below toolbar) |
| 482 // so card border doesn't look janky during animation. |
| 483 DCHECK([swipeDelegate_ verifyToolbarViewPlacementInView:gesture.view]); |
| 484 // Insert above the toolbar. |
| 485 [gesture.view addSubview:tabSideSwipeView_]; |
| 486 |
| 487 // Remove content area so it doesn't receive any pan events. |
| 488 [[swipeDelegate_ contentView] removeFromSuperview]; |
| 489 } |
| 490 |
| 491 [tabSideSwipeView_ handleHorizontalPan:gesture]; |
| 492 } |
| 493 |
| 494 - (void)addCurtainWithCompletionHandler:(ProceduralBlock)completionHandler { |
| 495 if (!curtain_) { |
| 496 curtain_.reset( |
| 497 [[UIView alloc] initWithFrame:[swipeDelegate_ contentView].bounds]); |
| 498 [curtain_ setBackgroundColor:[UIColor whiteColor]]; |
| 499 } |
| 500 [[swipeDelegate_ contentView] addSubview:curtain_]; |
| 501 |
| 502 // Fallback in case load takes a while. 3 seconds is a balance between how |
| 503 // long it can take a web view to clear the previous page image, and what |
| 504 // feels like to 'too long' to see the curtain. |
| 505 [self performSelector:@selector(dismissCurtainWithCompletionHandler:) |
| 506 withObject:[[completionHandler copy] autorelease] |
| 507 afterDelay:3]; |
| 508 } |
| 509 |
| 510 - (void)dismissCurtainWithCompletionHandler:(ProceduralBlock)completionHandler { |
| 511 [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
| 512 webStateObserverBridge_.reset(); |
| 513 [curtain_ removeFromSuperview]; |
| 514 curtain_.reset(); |
| 515 completionHandler(); |
| 516 } |
| 517 |
| 518 #pragma mark - CRWWebStateObserver Methods |
| 519 |
| 520 - (void)webStateDidStopLoading:(web::WebState*)webState { |
| 521 [self dismissCurtainWithCompletionHandler:^{ |
| 522 inSwipe_ = NO; |
| 523 }]; |
| 524 } |
| 525 |
| 526 #pragma mark - TabModelObserver Methods |
| 527 |
| 528 - (void)tabModel:(TabModel*)model |
| 529 didChangeActiveTab:(Tab*)newTab |
| 530 previousTab:(Tab*)previousTab |
| 531 atIndex:(NSUInteger)index { |
| 532 // Toggling the gesture's enabled state off and on will effectively cancel |
| 533 // the gesture recognizer. |
| 534 [swipeGestureRecognizer_ setEnabled:NO]; |
| 535 [swipeGestureRecognizer_ setEnabled:YES]; |
| 536 } |
| 537 |
| 538 @end |
OLD | NEW |