Index: ios/chrome/browser/ui/sad_tab/sad_tab_view.mm |
diff --git a/ios/chrome/browser/ui/sad_tab/sad_tab_view.mm b/ios/chrome/browser/ui/sad_tab/sad_tab_view.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f791f5de78735fb53caf1963b54bacf570e13443 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/sad_tab/sad_tab_view.mm |
@@ -0,0 +1,401 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// 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/sad_tab/sad_tab_view.h" |
+ |
+#import "base/ios/weak_nsobject.h" |
+#include "base/logging.h" |
+#include "base/mac/scoped_block.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "components/grit/components_scaled_resources.h" |
+#include "components/strings/grit/components_strings.h" |
+#include "ios/chrome/browser/chrome_url_constants.h" |
+#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" |
+#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h" |
+#import "ios/chrome/browser/ui/commands/open_url_command.h" |
+#include "ios/chrome/browser/ui/rtl_geometry.h" |
+#include "ios/chrome/browser/ui/ui_util.h" |
+#import "ios/chrome/browser/ui/url_loader.h" |
+#import "ios/chrome/browser/ui/util/label_link_controller.h" |
+#import "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.h" |
+#import "ios/third_party/material_components_ios/src/components/Palettes/src/MaterialPalettes.h" |
+#import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoFontLoader.h" |
+#include "ios/web/public/interstitials/web_interstitial.h" |
+#include "ios/web/public/web_state/web_state.h" |
+#import "net/base/mac/url_conversions.h" |
+#include "ui/base/l10n/l10n_util.h" |
+#include "ui/base/resource/resource_bundle.h" |
+#include "ui/gfx/image/image.h" |
+ |
+namespace { |
+// Color constants. |
+const CGFloat kBackgroundColorBrightness = 247.0f / 255.0f; |
+const CGFloat kTitleLabelTextColorBrightness = 22.0f / 255.0f; |
+const CGFloat kMessageLabelTextColorBrightness = 80.0f / 255.0f; |
+// Layout constants. |
+const UIEdgeInsets kLayoutInsets = {24.0f, 24.0f, 24.0f, 24.0f}; |
+const CGFloat kLayoutBoundsMaxWidth = 600.0f; |
+const CGFloat kContainerViewLandscapeTopPadding = 22.0f; |
+const CGFloat kTitleLabelTopPadding = 26.0f; |
+const CGFloat kMessageLabelTopPadding = 16.0f; |
+const CGFloat kHelpLabelTopPadding = 16.0f; |
+const CGFloat kReloadButtonHeight = 48.0f; |
+const CGFloat kReloadButtonTopPadding = 16.0f; |
+// Label font sizes. |
+const CGFloat kTitleLabelFontSize = 23.0f; |
+const CGFloat kMessageLabelFontSize = 14.0f; |
+const CGFloat kHelpLabelFontSize = 14.0f; |
+} // namespace |
+ |
+@interface SadTabView () { |
+ // The block called when |_reloadButton| is tapped. |
+ base::mac::ScopedBlock<ProceduralBlock> _reloadHandler; |
+ // Backing objects for properties of the same name. |
+ base::scoped_nsobject<UIView> _containerView; |
+ base::scoped_nsobject<UIImageView> _imageView; |
+ base::scoped_nsobject<UILabel> _titleLabel; |
+ base::scoped_nsobject<UILabel> _messageLabel; |
+ base::scoped_nsobject<UILabel> _helpLabel; |
+ base::scoped_nsobject<LabelLinkController> _helpLabelLinkController; |
+ base::scoped_nsobject<MDCButton> _reloadButton; |
+} |
+ |
+// Container view that displays all other subviews. |
+@property(nonatomic, readonly) UIView* containerView; |
+// Displays the Sad Tab face. |
+@property(nonatomic, readonly) UIImageView* imageView; |
+// Displays the Sad Tab title. |
+@property(nonatomic, readonly) UILabel* titleLabel; |
+// Displays the Sad Tab message. |
+@property(nonatomic, readonly) UILabel* messageLabel; |
+// Displays the Sad Tab help message. |
+@property(nonatomic, readonly) UILabel* helpLabel; |
+// Button used to trigger a reload. |
+@property(nonatomic, readonly) UIButton* reloadButton; |
+ |
+// The bounds of |containerView|, with a height updated to CGFLOAT_MAX to allow |
+// text to be laid out using as many lines as necessary. |
+@property(nonatomic, readonly) CGRect containerBounds; |
+ |
+// Subview layout methods. Must be called in the following order, as subsequent |
+// layouts reference the values set in previous functions. |
+- (void)layoutImageView; |
+- (void)layoutTitleLabel; |
+- (void)layoutMessageLabel; |
+- (void)layoutHelpLabel; |
+- (void)layoutReloadButton; |
+- (void)layoutContainerView; |
+ |
+// The action selector for |_reloadButton|. |
+- (void)handleReloadButtonTapped; |
+ |
+// Returns the desired background color. |
++ (UIColor*)sadTabBackgroundColor; |
+ |
+@end |
+ |
+#pragma mark - SadTabView |
+ |
+@implementation SadTabView |
+ |
+- (instancetype)initWithReloadHandler:(ProceduralBlock)reloadHandler { |
+ self = [super initWithFrame:CGRectZero]; |
+ if (self) { |
+ DCHECK(reloadHandler); |
+ _reloadHandler.reset([reloadHandler copy]); |
+ self.backgroundColor = [[self class] sadTabBackgroundColor]; |
+ } |
+ return self; |
+} |
+ |
+- (instancetype)init { |
+ NOTREACHED(); |
+ return nil; |
+} |
+ |
+- (instancetype)initWithFrame:(CGRect)frame { |
+ NOTREACHED(); |
+ return nil; |
+} |
+ |
+- (instancetype)initWithCoder:(NSCoder*)aDecoder { |
+ NOTREACHED(); |
+ return nil; |
+} |
+ |
+#pragma mark Accessors |
+ |
+- (UIView*)containerView { |
+ if (!_containerView) { |
+ _containerView.reset([[UIView alloc] initWithFrame:CGRectZero]); |
+ [_containerView setBackgroundColor:self.backgroundColor]; |
+ } |
+ return _containerView; |
+} |
+ |
+- (UIImageView*)imageView { |
+ if (!_imageView) { |
+ ui::ResourceBundle& resourceBundle = |
+ ui::ResourceBundle::GetSharedInstance(); |
+ UIImage* image = |
+ resourceBundle.GetNativeImageNamed(IDR_CRASH_SAD_TAB).ToUIImage(); |
+ _imageView.reset([[UIImageView alloc] initWithImage:image]); |
+ [_imageView setBackgroundColor:self.backgroundColor]; |
+ } |
+ return _imageView.get(); |
+} |
+ |
+- (UILabel*)titleLabel { |
+ if (!_titleLabel) { |
+ _titleLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); |
+ [_titleLabel setBackgroundColor:self.backgroundColor]; |
+ [_titleLabel setText:base::SysUTF8ToNSString( |
+ l10n_util::GetStringUTF8(IDS_SAD_TAB_TITLE))]; |
+ [_titleLabel setLineBreakMode:NSLineBreakByWordWrapping]; |
+ [_titleLabel setNumberOfLines:0]; |
+ [_titleLabel |
+ setTextColor:[UIColor colorWithWhite:kTitleLabelTextColorBrightness |
+ alpha:1.0]]; |
+ [_titleLabel setFont:[[MDFRobotoFontLoader sharedInstance] |
+ regularFontOfSize:kTitleLabelFontSize]]; |
+ } |
+ return _titleLabel.get(); |
+} |
+ |
+- (UILabel*)messageLabel { |
+ if (!_messageLabel) { |
+ _messageLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); |
+ [_messageLabel setBackgroundColor:self.backgroundColor]; |
+ std::string messageText = l10n_util::GetStringUTF8(IDS_SAD_TAB_MESSAGE); |
+ [_messageLabel setText:base::SysUTF8ToNSString(messageText)]; |
+ [_messageLabel setLineBreakMode:NSLineBreakByWordWrapping]; |
+ [_messageLabel setNumberOfLines:0]; |
+ [_messageLabel |
+ setTextColor:[UIColor colorWithWhite:kMessageLabelTextColorBrightness |
+ alpha:1.0]]; |
+ [_messageLabel setFont:[[MDFRobotoFontLoader sharedInstance] |
+ regularFontOfSize:kMessageLabelFontSize]]; |
+ } |
+ return _messageLabel.get(); |
+} |
+ |
+- (UILabel*)helpLabel { |
+ if (!_helpLabel) { |
+ _helpLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); |
+ [_helpLabel setBackgroundColor:self.backgroundColor]; |
+ [_helpLabel setNumberOfLines:0]; |
+ [_helpLabel setFont:[[MDFRobotoFontLoader sharedInstance] |
+ regularFontOfSize:kHelpLabelFontSize]]; |
+ [_helpLabel |
+ setTextColor:[UIColor colorWithWhite:kMessageLabelTextColorBrightness |
+ alpha:1.0]]; |
+ // Fetch help text. |
+ base::string16 helpLinkText( |
+ l10n_util::GetStringUTF16(IDS_SAD_TAB_HELP_LINK)); |
+ NSString* helpText = base::SysUTF16ToNSString( |
+ l10n_util::GetStringFUTF16(IDS_SAD_TAB_HELP_MESSAGE, helpLinkText)); |
+ [_helpLabel setText:helpText]; |
+ // Create link controller. |
+ base::WeakNSObject<SadTabView> weakSelf(self); |
+ _helpLabelLinkController.reset([[LabelLinkController alloc] |
+ initWithLabel:_helpLabel |
+ action:^(const GURL& url) { |
+ base::scoped_nsobject<OpenUrlCommand> openCommand( |
+ [[OpenUrlCommand alloc] initWithURLFromChrome:url]); |
+ [weakSelf chromeExecuteCommand:openCommand]; |
+ }]); |
+ [_helpLabelLinkController |
+ setLinkFont:[[MDFRobotoFontLoader sharedInstance] |
+ boldFontOfSize:kHelpLabelFontSize]]; |
+ [_helpLabelLinkController setLinkUnderlineStyle:NSUnderlineStyleSingle]; |
+ NSRange linkRange = |
+ [helpText rangeOfString:base::SysUTF16ToNSString(helpLinkText)]; |
+ DCHECK_NE(linkRange.location, static_cast<NSUInteger>(NSNotFound)); |
+ DCHECK_NE(linkRange.length, 0U); |
+ [_helpLabelLinkController addLinkWithRange:linkRange |
+ url:GURL(kCrashReasonURL)]; |
+ } |
+ return _helpLabel.get(); |
+} |
+ |
+- (UIButton*)reloadButton { |
+ if (!_reloadButton) { |
+ _reloadButton.reset([[MDCFlatButton alloc] init]); |
+ [_reloadButton setBackgroundColor:[[MDCPalette cr_bluePalette] tint500] |
+ forState:UIControlStateNormal]; |
+ [_reloadButton setBackgroundColor:[[MDCPalette greyPalette] tint500] |
+ forState:UIControlStateDisabled]; |
+ [_reloadButton setCustomTitleColor:[UIColor whiteColor]]; |
+ [_reloadButton setUnderlyingColorHint:[UIColor blackColor]]; |
+ [_reloadButton setInkColor:[UIColor colorWithWhite:1 alpha:0.2f]]; |
+ NSString* title = base::SysUTF8ToNSString( |
+ l10n_util::GetStringUTF8(IDS_SAD_TAB_RELOAD_LABEL)); |
+ [_reloadButton setTitle:title forState:UIControlStateNormal]; |
+ [_reloadButton setTitleColor:[UIColor whiteColor] |
+ forState:UIControlStateNormal]; |
+ [_reloadButton addTarget:self |
+ action:@selector(handleReloadButtonTapped) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ } |
+ return _reloadButton.get(); |
+} |
+ |
+- (CGRect)containerBounds { |
+ CGFloat containerWidth = std::min( |
+ CGRectGetWidth(self.bounds) - kLayoutInsets.left - kLayoutInsets.right, |
+ kLayoutBoundsMaxWidth); |
+ return CGRectMake(0.0, 0.0, containerWidth, CGFLOAT_MAX); |
+} |
+ |
+#pragma mark Layout |
+ |
+- (void)willMoveToSuperview:(nullable UIView*)newSuperview { |
+ [super willMoveToSuperview:newSuperview]; |
+ |
+ if (self.containerView.superview) { |
+ DCHECK_EQ(self.containerView.superview, self); |
+ return; |
+ } |
+ |
+ [self addSubview:self.containerView]; |
+ [self.containerView addSubview:self.imageView]; |
+ [self.containerView addSubview:self.titleLabel]; |
+ [self.containerView addSubview:self.messageLabel]; |
+ [self.containerView addSubview:self.helpLabel]; |
+} |
+ |
+- (void)layoutSubviews { |
+ [super layoutSubviews]; |
+ |
+ [self layoutImageView]; |
+ [self layoutTitleLabel]; |
+ [self layoutMessageLabel]; |
+ [self layoutHelpLabel]; |
+ [self layoutReloadButton]; |
+ [self layoutContainerView]; |
+} |
+ |
+- (CGSize)sizeThatFits:(CGSize)size { |
+ return size; |
+} |
+ |
+- (void)layoutImageView { |
+ LayoutRect imageViewLayout = LayoutRectZero; |
+ imageViewLayout.boundingWidth = CGRectGetWidth(self.containerBounds); |
+ imageViewLayout.size = self.imageView.bounds.size; |
+ self.imageView.frame = |
+ AlignRectOriginAndSizeToPixels(LayoutRectGetRect(imageViewLayout)); |
+} |
+ |
+- (void)layoutTitleLabel { |
+ CGRect containerBounds = self.containerBounds; |
+ LayoutRect titleLabelLayout = LayoutRectZero; |
+ titleLabelLayout.boundingWidth = CGRectGetWidth(containerBounds); |
+ titleLabelLayout.size = [self.titleLabel sizeThatFits:containerBounds.size]; |
+ titleLabelLayout.position.originY = |
+ CGRectGetMaxY(self.imageView.frame) + kTitleLabelTopPadding; |
+ self.titleLabel.frame = |
+ AlignRectOriginAndSizeToPixels(LayoutRectGetRect(titleLabelLayout)); |
+} |
+ |
+- (void)layoutMessageLabel { |
+ CGRect containerBounds = self.containerBounds; |
+ LayoutRect messageLabelLayout = LayoutRectZero; |
+ messageLabelLayout.boundingWidth = CGRectGetWidth(containerBounds); |
+ messageLabelLayout.size = |
+ [self.messageLabel sizeThatFits:containerBounds.size]; |
+ messageLabelLayout.position.originY = |
+ CGRectGetMaxY(self.titleLabel.frame) + kMessageLabelTopPadding; |
+ self.messageLabel.frame = |
+ AlignRectOriginAndSizeToPixels(LayoutRectGetRect(messageLabelLayout)); |
+} |
+ |
+- (void)layoutHelpLabel { |
+ CGRect containerBounds = self.containerBounds; |
+ LayoutRect helpLabelLayout = LayoutRectZero; |
+ helpLabelLayout.boundingWidth = CGRectGetWidth(containerBounds); |
+ helpLabelLayout.size = [self.helpLabel sizeThatFits:containerBounds.size]; |
+ helpLabelLayout.position.originY = |
+ CGRectGetMaxY(self.messageLabel.frame) + kHelpLabelTopPadding; |
+ self.helpLabel.frame = |
+ AlignRectOriginAndSizeToPixels(LayoutRectGetRect(helpLabelLayout)); |
+} |
+ |
+- (void)layoutReloadButton { |
+ CGRect containerBounds = self.containerBounds; |
+ BOOL isIPadIdiom = IsIPadIdiom(); |
+ BOOL isPortrait = IsPortrait(); |
+ BOOL shouldAddReloadButtonToContainer = isIPadIdiom || !isPortrait; |
+ LayoutRect reloadButtonLayout = LayoutRectZero; |
+ reloadButtonLayout.size = |
+ isIPadIdiom |
+ ? [self.reloadButton sizeThatFits:CGSizeZero] |
+ : CGSizeMake(CGRectGetWidth(containerBounds), kReloadButtonHeight); |
+ if (shouldAddReloadButtonToContainer) { |
+ // Right-align reloadButton and add it below helpLabel when adding it to |
+ // the containerView. |
+ if (self.reloadButton.superview != self.containerView) |
+ [self.containerView addSubview:self.reloadButton]; |
+ reloadButtonLayout.boundingWidth = CGRectGetWidth(containerBounds); |
+ reloadButtonLayout.position = LayoutRectPositionMake( |
+ CGRectGetWidth(containerBounds) - reloadButtonLayout.size.width, |
+ CGRectGetMaxY(self.helpLabel.frame) + kReloadButtonTopPadding); |
+ } else { |
+ // Bottom-align the reloadButton with the bounds specified by kLayoutInsets. |
+ if (self.reloadButton.superview != self) |
+ [self addSubview:self.reloadButton]; |
+ reloadButtonLayout.boundingWidth = CGRectGetWidth(self.bounds); |
+ reloadButtonLayout.position = LayoutRectPositionMake( |
+ UIEdgeInsetsGetLeading(kLayoutInsets), |
+ CGRectGetMaxY(self.bounds) - kLayoutInsets.bottom - |
+ reloadButtonLayout.size.height); |
+ } |
+ self.reloadButton.frame = |
+ AlignRectOriginAndSizeToPixels(LayoutRectGetRect(reloadButtonLayout)); |
+} |
+ |
+- (void)layoutContainerView { |
+ UIView* bottomSubview = self.reloadButton.superview == self.containerView |
+ ? self.reloadButton |
+ : self.helpLabel; |
+ CGSize containerSize = CGSizeMake(CGRectGetWidth(self.containerBounds), |
+ CGRectGetMaxY(bottomSubview.frame)); |
+ CGFloat containerOriginX = |
+ (CGRectGetWidth(self.bounds) - containerSize.width) / 2.0f; |
+ CGFloat containerOriginY = 0.0f; |
+ if (IsIPadIdiom()) { |
+ // Center the containerView on iPads. |
+ containerOriginY = |
+ (CGRectGetHeight(self.bounds) - containerSize.height) / 2.0f; |
+ } else if (IsPortrait()) { |
+ // Align containerView to a quarter of the view height on portrait iPhones. |
+ containerOriginY = |
+ (CGRectGetHeight(self.bounds) - containerSize.height) / 4.0f; |
+ } else { |
+ // Top-align containerView on landscape iPhones. |
+ containerOriginY = kContainerViewLandscapeTopPadding; |
+ } |
+ self.containerView.frame = AlignRectOriginAndSizeToPixels( |
+ CGRectMake(containerOriginX, containerOriginY, containerSize.width, |
+ containerSize.height)); |
+} |
+ |
+#pragma mark Util |
+ |
+- (void)handleHelpLabelLinkButtonTapped { |
+ base::scoped_nsobject<OpenUrlCommand> openCommand( |
+ [[OpenUrlCommand alloc] initWithURLFromChrome:GURL(kCrashReasonURL)]); |
+ [self chromeExecuteCommand:openCommand]; |
+} |
+ |
+- (void)handleReloadButtonTapped { |
+ _reloadHandler.get()(); |
+} |
+ |
++ (UIColor*)sadTabBackgroundColor { |
+ return [UIColor colorWithWhite:kBackgroundColorBrightness alpha:1.0]; |
+} |
+ |
+@end |