Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(103)

Side by Side Diff: ios/chrome/browser/ui/ntp/google_landing_controller.mm

Issue 2806153004: Convert main NTP panel to UIViewController. (Closed)
Patch Set: Comment typo Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #import "ios/chrome/browser/ui/ntp/google_landing_controller.h" 5 #import "ios/chrome/browser/ui/ntp/google_landing_controller.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 8
9 #include "base/i18n/case_conversion.h" 9 #include "base/i18n/case_conversion.h"
10 #import "base/ios/weak_nsobject.h" 10 #import "base/ios/weak_nsobject.h"
(...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after
184 184
185 @end 185 @end
186 186
187 @interface GoogleLandingController ()<MostVisitedSitesObserving, 187 @interface GoogleLandingController ()<MostVisitedSitesObserving,
188 OverscrollActionsControllerDelegate, 188 OverscrollActionsControllerDelegate,
189 UICollectionViewDataSource, 189 UICollectionViewDataSource,
190 UICollectionViewDelegate, 190 UICollectionViewDelegate,
191 UICollectionViewDelegateFlowLayout, 191 UICollectionViewDelegateFlowLayout,
192 UIGestureRecognizerDelegate, 192 UIGestureRecognizerDelegate,
193 WhatsNewHeaderViewDelegate> { 193 WhatsNewHeaderViewDelegate> {
194 // The main view.
195 base::scoped_nsobject<GoogleLandingView> _view;
196
197 // Fake omnibox. 194 // Fake omnibox.
198 base::scoped_nsobject<UIButton> _searchTapTarget; 195 base::scoped_nsobject<UIButton> _searchTapTarget;
199 196
200 // Controller to fetch and show doodles or a default Google logo. 197 // Controller to fetch and show doodles or a default Google logo.
201 base::scoped_nsprotocol<id<LogoVendor>> _doodleController; 198 base::scoped_nsprotocol<id<LogoVendor>> _doodleController;
202 199
203 // Most visited data from the MostVisitedSites service (copied upon receiving 200 // Most visited data from the MostVisitedSites service (copied upon receiving
204 // the callback). 201 // the callback).
205 ntp_tiles::NTPTilesVector _mostVisitedData; 202 ntp_tiles::NTPTilesVector _mostVisitedData;
206 203
(...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after
386 _tapGestureRecognizer.reset([[UITapGestureRecognizer alloc] 383 _tapGestureRecognizer.reset([[UITapGestureRecognizer alloc]
387 initWithTarget:self 384 initWithTarget:self
388 action:@selector(blurOmnibox)]); 385 action:@selector(blurOmnibox)]);
389 [_tapGestureRecognizer setDelegate:self]; 386 [_tapGestureRecognizer setDelegate:self];
390 _swipeGestureRecognizer.reset([[UISwipeGestureRecognizer alloc] 387 _swipeGestureRecognizer.reset([[UISwipeGestureRecognizer alloc]
391 initWithTarget:self 388 initWithTarget:self
392 action:@selector(blurOmnibox)]); 389 action:@selector(blurOmnibox)]);
393 [_swipeGestureRecognizer 390 [_swipeGestureRecognizer
394 setDirection:UISwipeGestureRecognizerDirectionDown]; 391 setDirection:UISwipeGestureRecognizerDirectionDown];
395 392
396 _view.reset(
397 [[GoogleLandingView alloc] initWithFrame:[UIScreen mainScreen].bounds]);
398 [_view setAutoresizingMask:UIViewAutoresizingFlexibleHeight |
399 UIViewAutoresizingFlexibleWidth];
400 [_view setFrameDelegate:self];
401
402 _focuser.reset(focuser); 393 _focuser.reset(focuser);
403 _webToolbarDelegate.reset(webToolbarDelegate); 394 _webToolbarDelegate.reset(webToolbarDelegate);
404 _tabModel.reset([tabModel retain]); 395 _tabModel.reset([tabModel retain]);
405 396
406 _scrolledToTop = NO; 397 _scrolledToTop = NO;
407 _animateHeader = YES; 398 _animateHeader = YES;
408 // Initialise |shiftTilesDownStartTime| to a sentinel value to indicate that
409 // the animation has not yet started.
410 _shiftTilesDownStartTime = -1;
411 _mostVisitedCellSize =
412 [GoogleLandingController mostVisitedCellSizeForView:_view];
413 [self addDoodle];
414 [self addSearchField];
415 [self addMostVisited];
416 [self addOverscrollActions];
417 [self reload];
418 } 399 }
419 return self; 400 return self;
420 } 401 }
421 402
403 - (void)loadView {
404 self.view =
405 [[GoogleLandingView alloc] initWithFrame:[UIScreen mainScreen].bounds];
406 [self.view setAutoresizingMask:UIViewAutoresizingFlexibleHeight |
407 UIViewAutoresizingFlexibleWidth];
408 [(GoogleLandingView*)self.view setFrameDelegate:self];
rohitrao (ping after 24h) 2017/04/12 12:45:09 You can redeclare the |view| property to be a Goog
justincohen 2017/04/12 14:23:12 Done.
409 // Initialise |shiftTilesDownStartTime| to a sentinel value to indicate that
410 // the animation has not yet started.
411 _shiftTilesDownStartTime = -1;
412 _mostVisitedCellSize =
413 [GoogleLandingController mostVisitedCellSizeForView:self.view];
414 [self addDoodle];
415 [self addSearchField];
416 [self addMostVisited];
417 [self addOverscrollActions];
418 [self reload];
419 }
420
422 + (CGSize)mostVisitedCellSizeForView:(UIView*)view { 421 + (CGSize)mostVisitedCellSizeForView:(UIView*)view {
423 if (IsIPadIdiom()) { 422 if (IsIPadIdiom()) {
424 // On iPads, split-screen and slide-over may require showing smaller cells. 423 // On iPads, split-screen and slide-over may require showing smaller cells.
425 CGSize maximumCellSize = [MostVisitedCell maximumSize]; 424 CGSize maximumCellSize = [MostVisitedCell maximumSize];
426 CGSize viewSize = view.bounds.size; 425 CGSize viewSize = view.bounds.size;
427 CGFloat smallestDimension = 426 CGFloat smallestDimension =
428 viewSize.height > viewSize.width ? viewSize.width : viewSize.height; 427 viewSize.height > viewSize.width ? viewSize.width : viewSize.height;
429 CGFloat cellWidth = AlignValueToPixel( 428 CGFloat cellWidth = AlignValueToPixel(
430 (smallestDimension - 3 * [self.class mostVisitedCellPadding]) / 2); 429 (smallestDimension - 3 * [self.class mostVisitedCellPadding]) / 2);
431 if (cellWidth < maximumCellSize.width) { 430 if (cellWidth < maximumCellSize.width) {
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
477 // Call -scrollViewDidScroll: so that the omnibox's frame is adjusted for 476 // Call -scrollViewDidScroll: so that the omnibox's frame is adjusted for
478 // the scroll view's offset. 477 // the scroll view's offset.
479 [self scrollViewDidScroll:_mostVisitedView]; 478 [self scrollViewDidScroll:_mostVisitedView];
480 }; 479 };
481 480
482 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), 481 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(),
483 layoutBlock); 482 layoutBlock);
484 } 483 }
485 484
486 - (CGFloat)viewWidth { 485 - (CGFloat)viewWidth {
487 return [_view frame].size.width; 486 return [self.view frame].size.width;
488 } 487 }
489 488
490 - (int)numberOfColumns { 489 - (int)numberOfColumns {
491 CGFloat width = [self viewWidth]; 490 CGFloat width = [self viewWidth];
492 CGFloat padding = [self.class mostVisitedCellPadding]; 491 CGFloat padding = [self.class mostVisitedCellPadding];
493 // Try to fit 4 columns. 492 // Try to fit 4 columns.
494 if (width >= 5 * padding + _mostVisitedCellSize.width * 4) 493 if (width >= 5 * padding + _mostVisitedCellSize.width * 4)
495 return 4; 494 return 4;
496 // Try to fit 3 columns. 495 // Try to fit 3 columns.
497 if (width >= 4 * padding + _mostVisitedCellSize.width * 3) 496 if (width >= 4 * padding + _mostVisitedCellSize.width * 3)
(...skipping 305 matching lines...) Expand 10 before | Expand all | Expand 10 after
803 } 802 }
804 803
805 - (void)resetSectionInset { 804 - (void)resetSectionInset {
806 UICollectionViewFlowLayout* flowLayout = 805 UICollectionViewFlowLayout* flowLayout =
807 (UICollectionViewFlowLayout*)[_mostVisitedView collectionViewLayout]; 806 (UICollectionViewFlowLayout*)[_mostVisitedView collectionViewLayout];
808 [flowLayout setSectionInset:UIEdgeInsetsZero]; 807 [flowLayout setSectionInset:UIEdgeInsetsZero];
809 } 808 }
810 809
811 - (void)updateSubviewFrames { 810 - (void)updateSubviewFrames {
812 _mostVisitedCellSize = 811 _mostVisitedCellSize =
813 [GoogleLandingController mostVisitedCellSizeForView:_view]; 812 [GoogleLandingController mostVisitedCellSizeForView:self.view];
814 UICollectionViewFlowLayout* flowLayout = 813 UICollectionViewFlowLayout* flowLayout =
815 base::mac::ObjCCastStrict<UICollectionViewFlowLayout>( 814 base::mac::ObjCCastStrict<UICollectionViewFlowLayout>(
816 [_mostVisitedView collectionViewLayout]); 815 [_mostVisitedView collectionViewLayout]);
817 [flowLayout setItemSize:_mostVisitedCellSize]; 816 [flowLayout setItemSize:_mostVisitedCellSize];
818 [[_doodleController view] setFrame:[self doodleFrame]]; 817 [[_doodleController view] setFrame:[self doodleFrame]];
819 818
820 [self setFlowLayoutInset:flowLayout]; 819 [self setFlowLayoutInset:flowLayout];
821 [flowLayout invalidateLayout]; 820 [flowLayout invalidateLayout];
822 [_promoHeaderView setSideMargin:[self leftMargin]]; 821 [_promoHeaderView setSideMargin:[self leftMargin]];
823 822
(...skipping 27 matching lines...) Expand all
851 850
852 if (!_viewLoaded) { 851 if (!_viewLoaded) {
853 _viewLoaded = YES; 852 _viewLoaded = YES;
854 [_doodleController fetchDoodle]; 853 [_doodleController fetchDoodle];
855 } 854 }
856 [self.delegate updateNtpBarShadowForPanelController:self]; 855 [self.delegate updateNtpBarShadowForPanelController:self];
857 } 856 }
858 857
859 // Initialize and add a panel with most visited sites. 858 // Initialize and add a panel with most visited sites.
860 - (void)addMostVisited { 859 - (void)addMostVisited {
861 CGRect mostVisitedFrame = [_view bounds]; 860 CGRect mostVisitedFrame = [self.view bounds];
862 base::scoped_nsobject<UICollectionViewFlowLayout> flowLayout; 861 base::scoped_nsobject<UICollectionViewFlowLayout> flowLayout;
863 if (IsIPadIdiom()) 862 if (IsIPadIdiom())
864 flowLayout.reset([[UICollectionViewFlowLayout alloc] init]); 863 flowLayout.reset([[UICollectionViewFlowLayout alloc] init]);
865 else 864 else
866 flowLayout.reset([[MostVisitedLayout alloc] init]); 865 flowLayout.reset([[MostVisitedLayout alloc] init]);
867 866
868 [flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical]; 867 [flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
869 [flowLayout setItemSize:_mostVisitedCellSize]; 868 [flowLayout setItemSize:_mostVisitedCellSize];
870 [flowLayout setMinimumInteritemSpacing:8]; 869 [flowLayout setMinimumInteritemSpacing:8];
871 [flowLayout setMinimumLineSpacing:[self.class mostVisitedCellPadding]]; 870 [flowLayout setMinimumLineSpacing:[self.class mostVisitedCellPadding]];
(...skipping 12 matching lines...) Expand all
884 [_mostVisitedView setShowsHorizontalScrollIndicator:NO]; 883 [_mostVisitedView setShowsHorizontalScrollIndicator:NO];
885 [_mostVisitedView setShowsVerticalScrollIndicator:NO]; 884 [_mostVisitedView setShowsVerticalScrollIndicator:NO];
886 [_mostVisitedView registerClass:[WhatsNewHeaderView class] 885 [_mostVisitedView registerClass:[WhatsNewHeaderView class]
887 forSupplementaryViewOfKind:UICollectionElementKindSectionHeader 886 forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
888 withReuseIdentifier:@"whatsNew"]; 887 withReuseIdentifier:@"whatsNew"];
889 [_mostVisitedView registerClass:[NewTabPageHeaderView class] 888 [_mostVisitedView registerClass:[NewTabPageHeaderView class]
890 forSupplementaryViewOfKind:UICollectionElementKindSectionHeader 889 forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
891 withReuseIdentifier:@"header"]; 890 withReuseIdentifier:@"header"];
892 [_mostVisitedView setAccessibilityIdentifier:@"Google Landing"]; 891 [_mostVisitedView setAccessibilityIdentifier:@"Google Landing"];
893 892
894 [_view addSubview:_mostVisitedView]; 893 [self.view addSubview:_mostVisitedView];
895 _most_visited_sites = 894 _most_visited_sites =
896 IOSMostVisitedSitesFactory::NewForBrowserState(_browserState); 895 IOSMostVisitedSitesFactory::NewForBrowserState(_browserState);
897 _most_visited_observer_bridge.reset( 896 _most_visited_observer_bridge.reset(
898 new ntp_tiles::MostVisitedSitesObserverBridge(self)); 897 new ntp_tiles::MostVisitedSitesObserverBridge(self));
899 _most_visited_sites->SetMostVisitedURLsObserver( 898 _most_visited_sites->SetMostVisitedURLsObserver(
900 _most_visited_observer_bridge.get(), kMaxNumMostVisitedFavicons); 899 _most_visited_observer_bridge.get(), kMaxNumMostVisitedFavicons);
901 } 900 }
902 901
903 - (void)updateSearchField { 902 - (void)updateSearchField {
904 NSArray* constraints = 903 NSArray* constraints =
(...skipping 26 matching lines...) Expand all
931 - (void)locationBarBecomesFirstResponder { 930 - (void)locationBarBecomesFirstResponder {
932 if (!_isShowing) 931 if (!_isShowing)
933 return; 932 return;
934 933
935 _omniboxFocused = YES; 934 _omniboxFocused = YES;
936 [self shiftTilesUp]; 935 [self shiftTilesUp];
937 } 936 }
938 937
939 - (void)shiftTilesUp { 938 - (void)shiftTilesUp {
940 _scrolledToTop = YES; 939 _scrolledToTop = YES;
941 // Add gesture recognizer to background |_view| when omnibox is focused. 940 // Add gesture recognizer to background |self.view| when omnibox is focused.
942 [_view addGestureRecognizer:_tapGestureRecognizer]; 941 [self.view addGestureRecognizer:_tapGestureRecognizer];
943 [_view addGestureRecognizer:_swipeGestureRecognizer]; 942 [self.view addGestureRecognizer:_swipeGestureRecognizer];
944 943
945 CGFloat pinnedOffsetY = [self pinnedOffsetY]; 944 CGFloat pinnedOffsetY = [self pinnedOffsetY];
946 _animateHeader = !IsIPadIdiom(); 945 _animateHeader = !IsIPadIdiom();
947 946
948 [UIView animateWithDuration:0.25 947 [UIView animateWithDuration:0.25
949 animations:^{ 948 animations:^{
950 if ([_mostVisitedView contentOffset].y < pinnedOffsetY) { 949 if ([_mostVisitedView contentOffset].y < pinnedOffsetY) {
951 [_mostVisitedView setContentOffset:CGPointMake(0, pinnedOffsetY)]; 950 [_mostVisitedView setContentOffset:CGPointMake(0, pinnedOffsetY)];
952 [[_mostVisitedView collectionViewLayout] invalidateLayout]; 951 [[_mostVisitedView collectionViewLayout] invalidateLayout];
953 } 952 }
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
998 [_searchTapTarget setHidden:NO]; 997 [_searchTapTarget setHidden:NO];
999 [_focuser onFakeboxBlur]; 998 [_focuser onFakeboxBlur];
1000 } 999 }
1001 1000
1002 // Reload most visited sites in case the number of placeholder cells needs to 1001 // Reload most visited sites in case the number of placeholder cells needs to
1003 // be updated after an orientation change. 1002 // be updated after an orientation change.
1004 [_mostVisitedView reloadData]; 1003 [_mostVisitedView reloadData];
1005 1004
1006 // Reshow views that are within range of the most visited collection view 1005 // Reshow views that are within range of the most visited collection view
1007 // (if necessary). 1006 // (if necessary).
1008 [_view removeGestureRecognizer:_tapGestureRecognizer]; 1007 [self.view removeGestureRecognizer:_tapGestureRecognizer];
1009 [_view removeGestureRecognizer:_swipeGestureRecognizer]; 1008 [self.view removeGestureRecognizer:_swipeGestureRecognizer];
1010 1009
1011 // CADisplayLink is used for this animation instead of the standard UIView 1010 // CADisplayLink is used for this animation instead of the standard UIView
1012 // animation because the standard animation did not properly convert the 1011 // animation because the standard animation did not properly convert the
1013 // fakebox from its scrolled up mode to its scrolled down mode. Specifically, 1012 // fakebox from its scrolled up mode to its scrolled down mode. Specifically,
1014 // calling |UICollectionView reloadData| adjacent to the standard animation 1013 // calling |UICollectionView reloadData| adjacent to the standard animation
1015 // caused the fakebox's views to jump incorrectly. CADisplayLink avoids this 1014 // caused the fakebox's views to jump incorrectly. CADisplayLink avoids this
1016 // problem because it allows |shiftTilesDownAnimationDidFire| to directly 1015 // problem because it allows |shiftTilesDownAnimationDidFire| to directly
1017 // control each frame. 1016 // control each frame.
1018 CADisplayLink* link = [CADisplayLink 1017 CADisplayLink* link = [CADisplayLink
1019 displayLinkWithTarget:self 1018 displayLinkWithTarget:self
(...skipping 463 matching lines...) Expand 10 before | Expand all | Expand 10 after
1483 [_contextMenuCoordinator stop]; 1482 [_contextMenuCoordinator stop];
1484 } 1483 }
1485 1484
1486 - (void)dismissKeyboard { 1485 - (void)dismissKeyboard {
1487 } 1486 }
1488 1487
1489 - (void)setScrollsToTop:(BOOL)enable { 1488 - (void)setScrollsToTop:(BOOL)enable {
1490 } 1489 }
1491 1490
1492 - (CGFloat)alphaForBottomShadow { 1491 - (CGFloat)alphaForBottomShadow {
1493 // Get the frame of the bottommost cell in |_view|'s coordinate system. 1492 // Get the frame of the bottommost cell in |self.view|'s coordinate system.
1494 NSInteger section = SectionWithMostVisited; 1493 NSInteger section = SectionWithMostVisited;
1495 // Account for the fact that the tableview may not yet contain 1494 // Account for the fact that the tableview may not yet contain
1496 // |numberOfNonEmptyTilesShown| tiles because it hasn't been updated yet. 1495 // |numberOfNonEmptyTilesShown| tiles because it hasn't been updated yet.
1497 NSUInteger lastItemIndex = 1496 NSUInteger lastItemIndex =
1498 std::min([_mostVisitedView numberOfItemsInSection:SectionWithMostVisited], 1497 std::min([_mostVisitedView numberOfItemsInSection:SectionWithMostVisited],
1499 [self numberOfNonEmptyTilesShown]) - 1498 [self numberOfNonEmptyTilesShown]) -
1500 1; 1499 1;
1501 DCHECK(lastItemIndex >= 0); 1500 DCHECK(lastItemIndex >= 0);
1502 NSIndexPath* lastCellIndexPath = 1501 NSIndexPath* lastCellIndexPath =
1503 [NSIndexPath indexPathForItem:lastItemIndex inSection:section]; 1502 [NSIndexPath indexPathForItem:lastItemIndex inSection:section];
1504 UICollectionViewLayoutAttributes* attributes = 1503 UICollectionViewLayoutAttributes* attributes =
1505 [_mostVisitedView layoutAttributesForItemAtIndexPath:lastCellIndexPath]; 1504 [_mostVisitedView layoutAttributesForItemAtIndexPath:lastCellIndexPath];
1506 CGRect lastCellFrame = attributes.frame; 1505 CGRect lastCellFrame = attributes.frame;
1507 CGRect cellFrameInSuperview = 1506 CGRect cellFrameInSuperview =
1508 [_mostVisitedView convertRect:lastCellFrame toView:self.view]; 1507 [_mostVisitedView convertRect:lastCellFrame toView:self.view];
1509 1508
1510 // Calculate when the bottom of the cell passes through the bottom of |_view|. 1509 // Calculate when the bottom of the cell passes through the bottom of
1510 // |self.view|.
1511 CGFloat maxY = CGRectGetMaxY(cellFrameInSuperview); 1511 CGFloat maxY = CGRectGetMaxY(cellFrameInSuperview);
1512 CGFloat viewHeight = CGRectGetHeight(self.view.frame); 1512 CGFloat viewHeight = CGRectGetHeight(self.view.frame);
1513 1513
1514 CGFloat pixelsBelowFrame = maxY - viewHeight; 1514 CGFloat pixelsBelowFrame = maxY - viewHeight;
1515 CGFloat alpha = pixelsBelowFrame / kNewTabPageDistanceToFadeShadow; 1515 CGFloat alpha = pixelsBelowFrame / kNewTabPageDistanceToFadeShadow;
1516 alpha = MIN(MAX(alpha, 0), 1); 1516 alpha = MIN(MAX(alpha, 0), 1);
1517 return alpha; 1517 return alpha;
1518 } 1518 }
1519 1519
1520 - (UIView*)view {
1521 return _view;
1522 }
1523
1524 #pragma mark - LogoAnimationControllerOwnerOwner 1520 #pragma mark - LogoAnimationControllerOwnerOwner
1525 1521
1526 - (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner { 1522 - (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
1527 return [_doodleController logoAnimationControllerOwner]; 1523 return [_doodleController logoAnimationControllerOwner];
1528 } 1524 }
1529 1525
1530 #pragma mark - UIScrollViewDelegate Methods. 1526 #pragma mark - UIScrollViewDelegate Methods.
1531 1527
1532 - (void)scrollViewDidScroll:(UIScrollView*)scrollView { 1528 - (void)scrollViewDidScroll:(UIScrollView*)scrollView {
1533 [self.delegate updateNtpBarShadowForPanelController:self]; 1529 [self.delegate updateNtpBarShadowForPanelController:self];
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after
1705 if (!view) { 1701 if (!view) {
1706 return nil; 1702 return nil;
1707 } 1703 }
1708 if ([view isKindOfClass:aClass]) { 1704 if ([view isKindOfClass:aClass]) {
1709 return view; 1705 return view;
1710 } 1706 }
1711 return [self nearestAncestorOfView:[view superview] withClass:aClass]; 1707 return [self nearestAncestorOfView:[view superview] withClass:aClass];
1712 } 1708 }
1713 1709
1714 @end 1710 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698