Skip to content

Commit

Permalink
[iOS] Add account picker sheet, configuration, layout delegate
Browse files Browse the repository at this point in the history
This CL is the first step to add the account picker UI for the Save to
Photos feature. This code is essentially copy-pasted from
`i/c/b/ui/**/consistency_promo_signin/consistency_sheet/`.

Fixed: 1474414
Change-Id: I0418d964c647e300506688d9048a9129beec339b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4798155
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Commit-Queue: Quentin Pubert <qpubert@google.com>
Reviewed-by: Jérôme Lebel <jlebel@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1187854}
  • Loading branch information
Quentin Pubert authored and Chromium LUCI CQ committed Aug 24, 2023
1 parent cc1382b commit 0edc57a
Show file tree
Hide file tree
Showing 15 changed files with 759 additions and 0 deletions.
15 changes: 15 additions & 0 deletions ios/chrome/browser/ui/account_picker/BUILD.gn
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

source_set("account_picker_configuration") {
sources = [
"account_picker_configuration.h",
"account_picker_configuration.mm",
]
}

source_set("account_picker_layout") {
sources = [ "account_picker_layout_delegate.h" ]
deps = [ "//ui/base" ]
}
1 change: 1 addition & 0 deletions ios/chrome/browser/ui/account_picker/OWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
jlebel@chromium.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef IOS_CHROME_BROWSER_UI_ACCOUNT_PICKER_ACCOUNT_PICKER_CONFIGURATION_H_
#define IOS_CHROME_BROWSER_UI_ACCOUNT_PICKER_ACCOUNT_PICKER_CONFIGURATION_H_

#import <Foundation/Foundation.h>

// Configuration for the AccountPickerCoordinator.
@interface AccountPickerConfiguration : NSObject

// Title of the account picker confirmation screen.
@property(nonatomic, copy) NSString* titleText;

// Body of the account picker confirmation screen which explains what the
// account will be used for.
@property(nonatomic, copy) NSString* bodyText;

// Title of the account picker confirmation screen submit button.
@property(nonatomic, copy) NSString* submitButtonTitle;

// The label of the "Ask every time" switch. If left nil, the switch will not be
// shown.
@property(nonatomic, copy) NSString* askEveryTimeSwitchLabelText;

@end

#endif // IOS_CHROME_BROWSER_UI_ACCOUNT_PICKER_ACCOUNT_PICKER_CONFIGURATION_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright 2023 The Chromium Authors
// 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/account_picker/account_picker_configuration.h"

@implementation AccountPickerConfiguration

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef IOS_CHROME_BROWSER_UI_ACCOUNT_PICKER_ACCOUNT_PICKER_LAYOUT_DELEGATE_H_
#define IOS_CHROME_BROWSER_UI_ACCOUNT_PICKER_ACCOUNT_PICKER_LAYOUT_DELEGATE_H_

#import <UIKit/UIKit.h>

// Style to display the account picker sheet.
enum AccountPickerSheetDisplayStyle {
// Bottom sheet at the bottom of the screen (for compact size).
kAccountPickerSheetDisplayStyleBottom,
// Bottom sheet centered in the middle of the screen (for regular size).
kAccountPickerSheetDisplayStyleCentered,
};

@protocol AccountPickerLayoutDelegate <NSObject>

// Display style according to the trait collection.
@property(nonatomic, assign, readonly)
AccountPickerSheetDisplayStyle displayStyle;

@end

#endif // IOS_CHROME_BROWSER_UI_ACCOUNT_PICKER_ACCOUNT_PICKER_LAYOUT_DELEGATE_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

source_set("account_picker_screen") {
sources = [
"account_picker_screen_constants.h",
"account_picker_screen_constants.mm",
"account_picker_screen_navigation_controller.h",
"account_picker_screen_navigation_controller.mm",
"account_picker_screen_presentation_controller.h",
"account_picker_screen_presentation_controller.mm",
"account_picker_screen_slide_transition_animator.h",
"account_picker_screen_slide_transition_animator.mm",
"account_picker_screen_view_controller.h",
]
deps = [
"//base",
"//ios/chrome/browser/shared/ui/util",
"//ios/chrome/browser/shared/ui/util/image",
"//ios/chrome/browser/ui/account_picker:account_picker_layout",
"//ios/chrome/common/ui/util",
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef IOS_CHROME_BROWSER_UI_ACCOUNT_PICKER_ACCOUNT_PICKER_SCREEN_ACCOUNT_PICKER_SCREEN_CONSTANTS_H_
#define IOS_CHROME_BROWSER_UI_ACCOUNT_PICKER_ACCOUNT_PICKER_SCREEN_ACCOUNT_PICKER_SCREEN_CONSTANTS_H_

#import <UIKit/UIKit.h>

// Maximum height ratio for the bottom sheet container view.
extern const CGFloat kMaxPickAccountHeightRatioWithWindow;

// AccountPickerScreenNavigationController accessibility identifier.
extern NSString* const
kAccountPickerScreenNavigationControllerAccessibilityIdentifier;

#endif // IOS_CHROME_BROWSER_UI_ACCOUNT_PICKER_ACCOUNT_PICKER_SCREEN_ACCOUNT_PICKER_SCREEN_CONSTANTS_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2023 The Chromium Authors
// 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/account_picker/account_picker_screen/account_picker_screen_constants.h"

const CGFloat kMaxPickAccountHeightRatioWithWindow = 0.75;

NSString* const
kAccountPickerScreenNavigationControllerAccessibilityIdentifier =
@"AccountPickerScreenNavigationControllerAccessibilityIdentifier";
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef IOS_CHROME_BROWSER_UI_ACCOUNT_PICKER_ACCOUNT_PICKER_SCREEN_ACCOUNT_PICKER_SCREEN_NAVIGATION_CONTROLLER_H_
#define IOS_CHROME_BROWSER_UI_ACCOUNT_PICKER_ACCOUNT_PICKER_SCREEN_ACCOUNT_PICKER_SCREEN_NAVIGATION_CONTROLLER_H_

#import <UIKit/UIKit.h>

#import "ios/chrome/browser/ui/account_picker/account_picker_layout_delegate.h"

// Delegate for updating navigation controller content.
@protocol AccountPickerScreenNavigationControllerLayoutDelegate <NSObject>

// Performs updates due to changes in preferred content size.
- (void)preferredContentSizeDidChangeForAccountPickerScreenViewController;

@end

// Navigation controller presented from the bottom. The pushed view controllers
// view have to be UIScrollView. This is required to support high font size
// (related to accessibility) with small devices (like iPhone SE).
// The view is automatically sized according to the last child view controller.
// This class works with AccountPickerScreenPresentationController and
// AccountPickerScreenSlideTransitionAnimator.
// Child view controller are required to implement
// AccountPickerScreenViewController protocol.
@interface AccountPickerScreenNavigationController
: UINavigationController <AccountPickerLayoutDelegate>

// Returns the desired size related to the current view controller shown by
// `AccountPickerScreenNavigationController`, based on `width`.
- (CGSize)layoutFittingSizeForWidth:(CGFloat)width;

// Updates internal views according to the consistency sheet view position.
- (void)didUpdateControllerViewFrame;

// Delegate for layout.
@property(nonatomic, weak)
id<AccountPickerScreenNavigationControllerLayoutDelegate>
layoutDelegate;
// Interaction transition to swipe from left to right to pop a view controller.
@property(nonatomic, strong, readonly)
UIPercentDrivenInteractiveTransition* interactionTransition;

@end

#endif // IOS_CHROME_BROWSER_UI_ACCOUNT_PICKER_ACCOUNT_PICKER_SCREEN_ACCOUNT_PICKER_SCREEN_NAVIGATION_CONTROLLER_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Copyright 2023 The Chromium Authors
// 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/account_picker/account_picker_screen/account_picker_screen_navigation_controller.h"

#import <algorithm>

#import "base/check.h"
#import "base/notreached.h"
#import "ios/chrome/browser/ui/account_picker/account_picker_screen/account_picker_screen_constants.h"
#import "ios/chrome/browser/ui/account_picker/account_picker_screen/account_picker_screen_view_controller.h"
#import "ios/chrome/common/ui/util/background_util.h"

namespace {

// Corner radius for centered style dialog.
constexpr CGFloat kCornerRadius = 12.;

} // namespace

@interface AccountPickerScreenNavigationController ()

// View to get transparent blurred background.
@property(nonatomic, strong, readwrite) UIView* backgroundView;
@property(nonatomic, strong, readwrite)
UIPercentDrivenInteractiveTransition* interactionTransition;

@end

@implementation AccountPickerScreenNavigationController

- (CGSize)layoutFittingSizeForWidth:(CGFloat)width {
UINavigationController* navigationController =
self.childViewControllers.lastObject;
DCHECK([navigationController
conformsToProtocol:@protocol(AccountPickerScreenViewController)]);
UIViewController<AccountPickerScreenViewController>*
childNavigationController =
static_cast<UIViewController<AccountPickerScreenViewController>*>(
navigationController);

// If the child controller updates its view due to an external action such
// as adding or removing an identity then force a layout to ensure the child
// height is up-to-date.
[childNavigationController.view setNeedsLayout];
[childNavigationController.view layoutIfNeeded];

CGFloat height =
[childNavigationController layoutFittingHeightForWidth:width];
CGFloat maxViewHeight =
self.view.window.frame.size.height * kMaxPickAccountHeightRatioWithWindow;
return CGSizeMake(width, std::min(height, maxViewHeight));
}

- (void)didUpdateControllerViewFrame {
self.backgroundView.frame = self.view.bounds;
}

- (void)preferredContentSizeDidChangeForChildContentContainer:
(id<UIContentContainer>)container {
[super preferredContentSizeDidChangeForChildContentContainer:container];
[self.layoutDelegate
preferredContentSizeDidChangeForAccountPickerScreenViewController];
}

#pragma mark - AccountPickerLayoutDelegate

- (AccountPickerSheetDisplayStyle)displayStyle {
return [self displayStyleWithTraitCollection:self.traitCollection];
}

#pragma mark - UIViewController

- (void)viewDidLoad {
[super viewDidLoad];
self.backgroundView = PrimaryBackgroundBlurView();
[self.view insertSubview:self.backgroundView atIndex:0];
self.backgroundView.frame = self.view.bounds;
self.view.layer.masksToBounds = YES;
self.view.clipsToBounds = YES;
self.view.accessibilityIdentifier =
kAccountPickerScreenNavigationControllerAccessibilityIdentifier;
UIScreenEdgePanGestureRecognizer* edgeSwipeGesture =
[[UIScreenEdgePanGestureRecognizer alloc]
initWithTarget:self
action:@selector(swipeAction:)];
edgeSwipeGesture.edges = UIRectEdgeLeft;
[self.view addGestureRecognizer:edgeSwipeGesture];
}

- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationBar setBackgroundImage:[[UIImage alloc] init]
forBarMetrics:UIBarMetricsDefault];
[self updateViewWithTraitCollection:self.traitCollection];
}

- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self didUpdateControllerViewFrame];
}

#pragma mark - UINavigationController

- (void)pushViewController:(UIViewController*)viewController
animated:(BOOL)animated {
DCHECK([viewController
conformsToProtocol:@protocol(AccountPickerScreenViewController)]);
[super pushViewController:viewController animated:animated];
}

#pragma mark - UIContentContainer

- (void)willTransitionToTraitCollection:(UITraitCollection*)newCollection
withTransitionCoordinator:
(id<UIViewControllerTransitionCoordinator>)coordinator {
[self updateViewWithTraitCollection:newCollection];
}

#pragma mark - SwipeGesture

// Called when the swipe gesture is active. This method controls the sliding
// between two view controls in `self`.
- (void)swipeAction:(UIScreenEdgePanGestureRecognizer*)gestureRecognizer {
if (!gestureRecognizer.view) {
self.interactionTransition = nil;
return;
}
UIView* view = gestureRecognizer.view;
CGFloat percentage =
[gestureRecognizer translationInView:view].x / view.bounds.size.width;
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan:
self.interactionTransition =
[[UIPercentDrivenInteractiveTransition alloc] init];
[self popViewControllerAnimated:YES];
[self.interactionTransition updateInteractiveTransition:percentage];
break;
case UIGestureRecognizerStateChanged:
[self.interactionTransition updateInteractiveTransition:percentage];
break;
case UIGestureRecognizerStateEnded:
if (percentage > .5 &&
gestureRecognizer.state != UIGestureRecognizerStateCancelled) {
[self.interactionTransition finishInteractiveTransition];
} else {
[self.interactionTransition cancelInteractiveTransition];
}
self.interactionTransition = nil;
break;
case UIGestureRecognizerStatePossible:
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed:
break;
}
}

#pragma mark - Private

// Updates the view according to the trait collection.
- (void)updateViewWithTraitCollection:(UITraitCollection*)collection {
switch ([self displayStyleWithTraitCollection:collection]) {
case kAccountPickerSheetDisplayStyleBottom:
self.view.layer.cornerRadius = 0;
break;
case kAccountPickerSheetDisplayStyleCentered:
self.view.layer.cornerRadius = kCornerRadius;
break;
}
}

// Returns the display style based on the trait collection.
- (AccountPickerSheetDisplayStyle)displayStyleWithTraitCollection:
(UITraitCollection*)collection {
// If one trait dimension is compact, the returned style is bottom.
// Otherwise, the returned style is centered.
BOOL hasAtLeastOneCompactSize =
(collection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) ||
(collection.verticalSizeClass == UIUserInterfaceSizeClassCompact);
return hasAtLeastOneCompactSize ? kAccountPickerSheetDisplayStyleBottom
: kAccountPickerSheetDisplayStyleCentered;
}

@end

0 comments on commit 0edc57a

Please sign in to comment.