chromium/ios/chrome/browser/badges/ui_bundled/badge_view_controller.mm

// Copyright 2019 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/badges/ui_bundled/badge_view_controller.h"

#import "base/check.h"
#import "ios/chrome/browser/badges/ui_bundled/badge_button.h"
#import "ios/chrome/browser/badges/ui_bundled/badge_button_factory.h"
#import "ios/chrome/browser/badges/ui_bundled/badge_constants.h"
#import "ios/chrome/browser/badges/ui_bundled/badge_item.h"
#import "ios/chrome/browser/badges/ui_bundled/badge_view_visibility_delegate.h"
#import "ios/chrome/browser/infobars/model/badge_state.h"
#import "ios/chrome/browser/infobars/model/infobar_ios.h"
#import "ios/chrome/browser/shared/ui/util/layout_guide_names.h"
#import "ios/chrome/browser/shared/ui/util/util_swift.h"
#import "ios/chrome/common/material_timing.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"

namespace {

// FullScreen progress threshold in which to toggle between full screen on and
// off mode for the badge view.
const CGFloat kFullScreenProgressThreshold = 0.85;

// Spacing between the top and trailing anchors of `unreadIndicatorView` and
// `displayedBadge`.
const CGFloat kUnreadIndicatorViewSpacing = 10.0;

// Height of `unreadIndicatorView`.
const CGFloat kUnreadIndicatorViewHeight = 6.0;

// Damping ratio of animating a change to the displayed badge.
const CGFloat kUpdateDisplayedBadgeAnimationDamping = 0.85;

}  // namespace

@interface BadgeViewController ()

// Button factory.
@property(nonatomic, strong) BadgeButtonFactory* buttonFactory;

// BadgeButton to show when in FullScreen (i.e. when the
// toolbars are expanded). Setting this property will add the button to the
// StackView.
@property(nonatomic, strong) BadgeButton* displayedBadge;

// BadgeButton to show in both FullScreen and non FullScreen.
@property(nonatomic, strong) BadgeButton* fullScreenBadge;

// StackView holding the displayedBadge and fullScreenBadge.
@property(nonatomic, strong) UIStackView* stackView;

// View that displays a blue dot on the top-right corner of the displayed badge
// if there are unread badges to be shown in the overflow menu.
@property(nonatomic, strong) UIView* unreadIndicatorView;

@end

@implementation BadgeViewController

- (instancetype)initWithButtonFactory:(BadgeButtonFactory*)buttonFactory {
  self = [super initWithNibName:nil bundle:nil];
  if (self) {
    DCHECK(buttonFactory);
    _buttonFactory = buttonFactory;
    _stackView = [[UIStackView alloc] init];
  }
  return self;
}

- (void)viewDidLoad {
  [super viewDidLoad];
  self.stackView.translatesAutoresizingMaskIntoConstraints = NO;
  self.stackView.axis = UILayoutConstraintAxisHorizontal;
  [self.view addSubview:self.stackView];
  AddSameConstraints(self.view, self.stackView);
}

#pragma mark - Protocols

#pragma mark BadgeConsumer

- (void)setupWithDisplayedBadge:(id<BadgeItem>)displayedBadgeItem
                fullScreenBadge:(id<BadgeItem>)fullscreenBadgeItem {
  self.displayedBadge = nil;
  self.fullScreenBadge = nil;
  if (displayedBadgeItem) {
    BadgeButton* newButton =
        [self.buttonFactory badgeButtonForBadgeType:displayedBadgeItem.badgeType
                                       usingInfoBar:nil];
    [newButton setAccepted:displayedBadgeItem.badgeState & BadgeStateAccepted
                  animated:NO];
    self.displayedBadge = newButton;
  }
  if (fullscreenBadgeItem) {
    self.fullScreenBadge = [self.buttonFactory
        badgeButtonForBadgeType:fullscreenBadgeItem.badgeType
                   usingInfoBar:nil];
  }
}

- (void)updateDisplayedBadge:(id<BadgeItem>)displayedBadgeItem
             fullScreenBadge:(id<BadgeItem>)fullscreenBadgeItem
                     infoBar:(InfoBarIOS*)infoBar {
  if (fullscreenBadgeItem) {
    if (!self.fullScreenBadge ||
        self.fullScreenBadge.badgeType != fullscreenBadgeItem.badgeType) {
      BadgeButton* newButton = [self.buttonFactory
          badgeButtonForBadgeType:fullscreenBadgeItem.badgeType
                     usingInfoBar:nil];
      self.fullScreenBadge = newButton;
    }
  } else {
    self.fullScreenBadge = nil;
  }

  if (displayedBadgeItem) {
    if (self.displayedBadge &&
        self.displayedBadge.badgeType == displayedBadgeItem.badgeType) {
      [self.displayedBadge
          setAccepted:displayedBadgeItem.badgeState & BadgeStateAccepted
             animated:YES];
    } else {
      BadgeButton* newButton = [self.buttonFactory
          badgeButtonForBadgeType:displayedBadgeItem.badgeType
                     usingInfoBar:infoBar];
      [newButton setAccepted:displayedBadgeItem.badgeState & BadgeStateAccepted
                    animated:NO];
      self.displayedBadge = newButton;
    }
    // Disable button if banner is being displayed.
    [self.displayedBadge
        setEnabled:!(displayedBadgeItem.badgeState & BadgeStatePresented)];
  } else {
    self.displayedBadge = nil;
  }
}

- (void)markDisplayedBadgeAsRead:(BOOL)read {
  // Lazy init if the unread indicator needs to be shown.
  if (!self.unreadIndicatorView && !read) {
    // Add unread indicator to the displayed badge.
    self.unreadIndicatorView = [[UIView alloc] init];
    self.unreadIndicatorView.layer.cornerRadius =
        kUnreadIndicatorViewHeight / 2;
    self.unreadIndicatorView.backgroundColor = [UIColor colorNamed:kBlueColor];
    self.unreadIndicatorView.translatesAutoresizingMaskIntoConstraints = NO;
    self.unreadIndicatorView.accessibilityIdentifier =
        kBadgeUnreadIndicatorAccessibilityIdentifier;
    [_displayedBadge addSubview:self.unreadIndicatorView];
    [NSLayoutConstraint activateConstraints:@[
      [self.unreadIndicatorView.trailingAnchor
          constraintEqualToAnchor:_displayedBadge.trailingAnchor
                         constant:-kUnreadIndicatorViewSpacing],
      [self.unreadIndicatorView.topAnchor
          constraintEqualToAnchor:_displayedBadge.topAnchor
                         constant:kUnreadIndicatorViewSpacing],
      [self.unreadIndicatorView.heightAnchor
          constraintEqualToConstant:kUnreadIndicatorViewHeight],
      [self.unreadIndicatorView.heightAnchor
          constraintEqualToAnchor:self.unreadIndicatorView.widthAnchor]
    ]];
  }
  if (self.unreadIndicatorView) {
    self.unreadIndicatorView.hidden = read;
  }
}

#pragma mark FullscreenUIElement

- (void)updateForFullscreenProgress:(CGFloat)progress {
  BOOL badgeViewShouldCollapse = progress <= kFullScreenProgressThreshold;
  if (badgeViewShouldCollapse) {
    self.fullScreenBadge.fullScreenOn = YES;
    // Fade in/out in the FullScreen badge with the FullScreen on
    // configurations.
    CGFloat alphaValue = fmax((kFullScreenProgressThreshold - progress) /
                                  kFullScreenProgressThreshold,
                              0);
    self.fullScreenBadge.alpha = alphaValue;
    self.displayedBadge.hidden = YES;
  } else {
    self.fullScreenBadge.fullScreenOn = NO;
    // Fade in/out the FullScreen badge with the FullScreen off configurations
    // at a speed matching that of the trailing button in the
    // LocationBarSteadyView.
    CGFloat alphaValue = fmax((progress - kFullScreenProgressThreshold) /
                                  (1 - kFullScreenProgressThreshold),
                              0);
    self.displayedBadge.hidden = NO;
    self.displayedBadge.alpha = alphaValue;
    self.fullScreenBadge.alpha = alphaValue;
  }
}

#pragma mark - Getter/Setter

- (void)setDisplayedBadge:(BadgeButton*)badgeButton {
  if (badgeButton.badgeType == self.displayedBadge.badgeType) {
    return;
  }

  [self.stackView removeArrangedSubview:_displayedBadge];
  [_displayedBadge removeFromSuperview];
  if (!badgeButton) {
    _displayedBadge = nil;
    self.unreadIndicatorView = nil;
    [self.visibilityDelegate setBadgeViewHidden:YES];
    return;
  }
  _displayedBadge = badgeButton;

  // Configure the initial state of the animation.
  self.view.alpha = 0;
  self.view.transform = CGAffineTransformMakeScale(0.1, 0.1);
  [self.stackView addArrangedSubview:_displayedBadge];
  [UIView animateWithDuration:kMaterialDuration2
                        delay:0
       usingSpringWithDamping:kUpdateDisplayedBadgeAnimationDamping
        initialSpringVelocity:0
                      options:UIViewAnimationOptionBeginFromCurrentState
                   animations:^{
                     self.view.alpha = 1;
                     self.view.transform = CGAffineTransformIdentity;
                   }
                   completion:nil];
  [self.visibilityDelegate setBadgeViewHidden:NO];
}

- (void)setFullScreenBadge:(BadgeButton*)fullScreenBadge {
  [self.stackView removeArrangedSubview:_fullScreenBadge];
  [_fullScreenBadge removeFromSuperview];
  if (!fullScreenBadge) {
    _fullScreenBadge = nil;
    return;
  }
  _fullScreenBadge = fullScreenBadge;
  [self.stackView insertArrangedSubview:_fullScreenBadge atIndex:0];
}

@end