chromium/ios/chrome/share_extension/share_extension_view.mm

// Copyright 2016 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/share_extension/share_extension_view.h"

#import "base/apple/bundle_locations.h"
#import "base/check.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/util/ui_util.h"
#import "ios/chrome/share_extension/ui_util.h"

namespace {

const CGFloat kCornerRadius = 6;
// Minimum size around the widget
const CGFloat kDividerHeight = 0.5;
const CGFloat kShareExtensionPadding = 16;
const CGFloat kButtonHeight = 44;

// Size of the icon if present.
const CGFloat kScreenshotSize = 80;

// Size for the buttons font.
const CGFloat kButtonFontSize = 17;

}  // namespace

#pragma mark - Share Extension Button

// UIButton with the background color changing when it is highlighted.
@interface ShareExtensionButton : UIButton
@end

@implementation ShareExtensionButton

- (void)setHighlighted:(BOOL)highlighted {
  [super setHighlighted:highlighted];

  if (highlighted)
    self.backgroundColor = [UIColor colorNamed:kTableViewRowHighlightColor];
  else
    self.backgroundColor = UIColor.clearColor;
}

@end

#pragma mark - Share Extension View

@interface ShareExtensionView ()

// Keep strong references of the views that need to be updated.
@property(nonatomic, strong) UILabel* titleLabel;
@property(nonatomic, strong) UILabel* URLLabel;
@property(nonatomic, strong) UIView* titleURLContainer;
@property(nonatomic, strong) UIButton* readingListButton;
@property(nonatomic, strong) UIImageView* screenshotView;
@property(nonatomic, strong) UIView* itemView;

@property(nonatomic, weak) id<ShareExtensionViewActionTarget> target;

// Track if a button has been pressed. All button pressing will have no effect
// if `dismissed` is YES.
@property(nonatomic, assign) BOOL dismissed;

@end

@implementation ShareExtensionView

#pragma mark - Lifecycle

- (instancetype)initWithActionTarget:
    (id<ShareExtensionViewActionTarget>)target {
  self = [super initWithFrame:CGRectZero];
  if (self) {
    DCHECK(target);
    _target = target;

    [self.layer setCornerRadius:kCornerRadius];
    [self setClipsToBounds:YES];

    self.backgroundColor = [UIColor colorNamed:kBackgroundColor];

    NSString* addToReadingListTitle = NSLocalizedString(
        @"IDS_IOS_ADD_READING_LIST_SHARE_EXTENSION",
        @"The add to reading list button text in share extension.");
    self.readingListButton =
        [self buttonWithTitle:addToReadingListTitle
                     selector:@selector(addToReadingListPressed:)];

    NSString* addToBookmarksTitle = NSLocalizedString(
        @"IDS_IOS_ADD_BOOKMARKS_SHARE_EXTENSION",
        @"The Add to bookmarks button text in share extension.");
    UIButton* bookmarksButton =
        [self buttonWithTitle:addToBookmarksTitle
                     selector:@selector(addToBookmarksPressed:)];

    NSString* openInChromeTitle = NSLocalizedString(
        @"IDS_IOS_OPEN_IN_CHROME_SHARE_EXTENSION",
        @"The Open in Chrome button text in share extension.");
    UIButton* openButton =
        [self buttonWithTitle:openInChromeTitle
                     selector:@selector(openInChromePressed:)];

    for (UIButton* button in
         @[ self.readingListButton, bookmarksButton, openButton ]) {
      button.pointerInteractionEnabled = YES;
      button.pointerStyleProvider = ^UIPointerStyle*(
          UIButton* theButton, __unused UIPointerEffect* proposedEffect,
          __unused UIPointerShape* proposedShape) {
        UITargetedPreview* preview =
            [[UITargetedPreview alloc] initWithView:theButton];
        UIPointerHoverEffect* effect =
            [UIPointerHoverEffect effectWithPreview:preview];
        return [UIPointerStyle styleWithEffect:effect shape:nil];
      };
    }

    UIStackView* contentStack = [[UIStackView alloc] initWithArrangedSubviews:@[
      [self navigationBar], [self dividerView], [self sharedItemView],
      [self dividerView], self.readingListButton, [self dividerView],
      bookmarksButton, [self dividerView], openButton
    ]];
    [contentStack setAxis:UILayoutConstraintAxisVertical];
    [self addSubview:contentStack];

    [contentStack setTranslatesAutoresizingMaskIntoConstraints:NO];

    ui_util::ConstrainAllSidesOfViewToView(self, contentStack);
  }
  return self;
}

#pragma mark Init helpers

// Returns a view containing the shared items (title, URL, screenshot). This
// method will set the ivars.
- (UIView*)sharedItemView {
  // Title label. Text will be filled by `setTitle:` when available.
  _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
  _titleLabel.font = [UIFont boldSystemFontOfSize:16];
  _titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
  _titleLabel.textColor = [UIColor colorNamed:kTextPrimaryColor];

  // URL label. Text will be filled by `setURL:` when available.
  _URLLabel = [[UILabel alloc] initWithFrame:CGRectZero];
  _URLLabel.translatesAutoresizingMaskIntoConstraints = NO;
  _URLLabel.numberOfLines = 3;
  _URLLabel.lineBreakMode = NSLineBreakByWordWrapping;
  _URLLabel.font = [UIFont systemFontOfSize:12];
  _URLLabel.textColor = [UIColor colorNamed:kTextPrimaryColor];

  // Screenshot view. Image will be filled by `setScreenshot:` when available.
  _screenshotView = [[UIImageView alloc] initWithFrame:CGRectZero];
  [_screenshotView setTranslatesAutoresizingMaskIntoConstraints:NO];
  NSLayoutConstraint* imageWidthConstraint =
      [_screenshotView.widthAnchor constraintEqualToConstant:0];
  imageWidthConstraint.priority = UILayoutPriorityDefaultHigh;
  imageWidthConstraint.active = YES;

  [_screenshotView.heightAnchor
      constraintEqualToAnchor:_screenshotView.widthAnchor]
      .active = YES;
  [_screenshotView setContentMode:UIViewContentModeScaleAspectFill];
  [_screenshotView setClipsToBounds:YES];

  // `_screenshotView` should take as much space as needed. Lower compression
  // resistance of the other elements.
  [_titleLabel
      setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
                                      forAxis:UILayoutConstraintAxisHorizontal];
  [_titleLabel setContentHuggingPriority:UILayoutPriorityDefaultHigh
                                 forAxis:UILayoutConstraintAxisVertical];
  [_URLLabel setContentHuggingPriority:UILayoutPriorityDefaultHigh
                               forAxis:UILayoutConstraintAxisVertical];

  [_URLLabel
      setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
                                      forAxis:UILayoutConstraintAxisHorizontal];

  _titleURLContainer = [[UIView alloc] initWithFrame:CGRectZero];
  [_titleURLContainer setTranslatesAutoresizingMaskIntoConstraints:NO];

  [_titleURLContainer addSubview:_titleLabel];
  [_titleURLContainer addSubview:_URLLabel];

  _itemView = [[UIView alloc] init];
  [_itemView setTranslatesAutoresizingMaskIntoConstraints:NO];
  [_itemView addSubview:_titleURLContainer];
  [_itemView addSubview:_screenshotView];

  [NSLayoutConstraint activateConstraints:@[
    [_titleLabel.topAnchor
        constraintEqualToAnchor:_titleURLContainer.topAnchor],
    [_URLLabel.topAnchor constraintEqualToAnchor:_titleLabel.bottomAnchor],
    [_URLLabel.bottomAnchor
        constraintEqualToAnchor:_titleURLContainer.bottomAnchor],
    [_titleLabel.trailingAnchor
        constraintEqualToAnchor:_titleURLContainer.trailingAnchor],
    [_URLLabel.trailingAnchor
        constraintEqualToAnchor:_titleURLContainer.trailingAnchor],
    [_titleLabel.leadingAnchor
        constraintEqualToAnchor:_titleURLContainer.leadingAnchor],
    [_URLLabel.leadingAnchor
        constraintEqualToAnchor:_titleURLContainer.leadingAnchor],
    [_titleURLContainer.centerYAnchor
        constraintEqualToAnchor:_itemView.centerYAnchor],
    [_itemView.heightAnchor
        constraintGreaterThanOrEqualToAnchor:_titleURLContainer.heightAnchor
                                    constant:2 * kShareExtensionPadding],
    [_titleURLContainer.leadingAnchor
        constraintEqualToAnchor:_itemView.leadingAnchor
                       constant:kShareExtensionPadding],
    [_screenshotView.trailingAnchor
        constraintEqualToAnchor:_itemView.trailingAnchor
                       constant:-kShareExtensionPadding],
    [_itemView.heightAnchor
        constraintGreaterThanOrEqualToAnchor:_screenshotView.heightAnchor
                                    constant:2 * kShareExtensionPadding],
    [_screenshotView.centerYAnchor
        constraintEqualToAnchor:_itemView.centerYAnchor],
  ]];

  NSLayoutConstraint* titleURLScreenshotConstraint =
      [_titleURLContainer.trailingAnchor
          constraintEqualToAnchor:_screenshotView.leadingAnchor];
  titleURLScreenshotConstraint.priority = UILayoutPriorityDefaultHigh;
  titleURLScreenshotConstraint.active = YES;

  return _itemView;
}

// Returns a view containing a divider.
- (UIView*)dividerView {
  UIView* divider = [[UIView alloc] initWithFrame:CGRectZero];
  [divider setTranslatesAutoresizingMaskIntoConstraints:NO];
  divider.backgroundColor = [UIColor colorNamed:kSeparatorColor];
  CGFloat slidingConstant = AlignValueToPixel(kDividerHeight);
  [divider.heightAnchor constraintEqualToConstant:slidingConstant].active = YES;
  return divider;
}

// Returns a button containing title `title` and action `selector` on
// `self.target`.
- (UIButton*)buttonWithTitle:(NSString*)title selector:(SEL)selector {
  UIButton* button = [[ShareExtensionButton alloc] initWithFrame:CGRectZero];
  [button setTitle:title forState:UIControlStateNormal];
  [button setTitleColor:[UIColor colorNamed:kBlueColor]
               forState:UIControlStateNormal];
  [[button titleLabel] setFont:[UIFont systemFontOfSize:kButtonFontSize]];
  [button setTranslatesAutoresizingMaskIntoConstraints:NO];
  [button addTarget:self
                action:selector
      forControlEvents:UIControlEventTouchUpInside];
  [button.heightAnchor constraintEqualToConstant:kButtonHeight].active = YES;
  return button;
}

// Returns a navigationBar.
- (UINavigationBar*)navigationBar {
  // Create the navigation bar.
  UINavigationBar* navigationBar =
      [[UINavigationBar alloc] initWithFrame:CGRectZero];
  [[navigationBar layer] setCornerRadius:kCornerRadius];
  [navigationBar setClipsToBounds:YES];

  // Create an empty image to replace the standard gray background of the
  // UINavigationBar.
  UIImage* emptyImage = [[UIImage alloc] init];
  [navigationBar setBackgroundImage:emptyImage
                      forBarMetrics:UIBarMetricsDefault];
  [navigationBar setShadowImage:emptyImage];
  [navigationBar setTranslucent:YES];
  [navigationBar setTranslatesAutoresizingMaskIntoConstraints:NO];
  navigationBar.titleTextAttributes = @{
    NSForegroundColorAttributeName : [UIColor colorNamed:kTextPrimaryColor]
  };

  UIBarButtonItem* cancelButton = [[UIBarButtonItem alloc]
      initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
                           target:self
                           action:@selector(cancelPressed:)];

  NSString* appName = [base::apple::FrameworkBundle()
      objectForInfoDictionaryKey:@"CFBundleDisplayName"];
  UINavigationItem* titleItem =
      [[UINavigationItem alloc] initWithTitle:appName];
  [titleItem setLeftBarButtonItem:cancelButton];
  [titleItem setHidesBackButton:YES];
  [navigationBar pushNavigationItem:titleItem animated:NO];
  return navigationBar;
}

// Called when "Add to Reading List" button has been pressed.
- (void)addToReadingListPressed:(UIButton*)sender {
  if (self.dismissed) {
    return;
  }
  self.dismissed = YES;
  [self
      animateButtonPressed:sender
            withCompletion:^{
              [self.target shareExtensionViewDidSelectAddToReadingList:sender];
            }];
}

// Called when "Add to bookmarks" button has been pressed.
- (void)addToBookmarksPressed:(UIButton*)sender {
  if (self.dismissed) {
    return;
  }
  self.dismissed = YES;
  [self animateButtonPressed:sender
              withCompletion:^{
                [self.target shareExtensionViewDidSelectAddToBookmarks:sender];
              }];
}

- (void)openInChromePressed:(UIButton*)sender {
  if (self.dismissed) {
    return;
  }
  self.dismissed = YES;
  [self.target shareExtensionViewDidSelectOpenInChrome:sender];
}

// Animates the button `sender` by replacing its string to "Added", then call
// completion.
- (void)animateButtonPressed:(UIButton*)sender
              withCompletion:(void (^)(void))completion {
  NSString* addedString =
      NSLocalizedString(@"IDS_IOS_ADDED_ITEM_SHARE_EXTENSION",
                        @"Button label after being pressed.");
  NSString* addedCheckedString =
      [addedString stringByAppendingString:@" \u2713"];
  // Create a label with the same style as the split animation between the text
  // and the checkmark.
  UILabel* addedLabel = [[UILabel alloc] initWithFrame:CGRectZero];
  [addedLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
  [addedLabel setText:addedString];
  [self addSubview:addedLabel];
  [addedLabel setFont:[sender titleLabel].font];
  [addedLabel setTextColor:[sender titleColorForState:UIControlStateNormal]];
  [addedLabel.leadingAnchor
      constraintEqualToAnchor:[sender titleLabel].leadingAnchor]
      .active = YES;
  [addedLabel.centerYAnchor
      constraintEqualToAnchor:[sender titleLabel].centerYAnchor]
      .active = YES;
  [addedLabel setAlpha:0];

  void (^step3ShowCheck)() = ^{
    [UIView animateWithDuration:ui_util::kAnimationDuration
        animations:^{
          [addedLabel setAlpha:0];
          [sender setAlpha:1];
        }
        completion:^(BOOL finished) {
          if (completion) {
            completion();
          }
        }];
  };

  void (^step2ShowTextWithoutCheck)() = ^{
    [sender setTitle:addedCheckedString forState:UIControlStateNormal];
    [UIView animateWithDuration:ui_util::kAnimationDuration
        animations:^{
          [addedLabel setAlpha:1];
        }
        completion:^(BOOL finished) {
          step3ShowCheck();
        }];
  };

  void (^step1HideText)() = ^{
    [UIView animateWithDuration:ui_util::kAnimationDuration
        animations:^{
          [sender setAlpha:0];
        }
        completion:^(BOOL finished) {
          step2ShowTextWithoutCheck();
        }];
  };
  step1HideText();
}

// Called when "Cancel" button has been pressed.
- (void)cancelPressed:(UIButton*)sender {
  if (self.dismissed) {
    return;
  }
  self.dismissed = YES;
  [self.target shareExtensionViewDidSelectCancel:sender];
}

#pragma mark - Content getters and setters.

- (void)setURL:(NSURL*)URL {
  [[self URLLabel] setText:[URL absoluteString]];
}

- (void)setTitle:(NSString*)title {
  [[self titleLabel] setText:title];
}

- (void)setScreenshot:(UIImage*)screenshot {
  [self.screenshotView.widthAnchor constraintEqualToConstant:kScreenshotSize]
      .active = YES;
  [self.titleURLContainer.trailingAnchor
      constraintEqualToAnchor:self.screenshotView.leadingAnchor
                     constant:-kShareExtensionPadding]
      .active = YES;
  [[self screenshotView] setImage:screenshot];
}

@end