Commit 6dac5f26 authored by Elodie Banel's avatar Elodie Banel Committed by Commit Bot

Content widget UI, not including use of real data.

The next CL will use the real data to update the UI. See screenshots
linked below for what it looks like with this CL and test data.

https://drive.google.com/a/chromium.org/file/d/0BxrfNSu8yqJqZG9reWl3T0FCclE/view?usp=sharing
https://drive.google.com/a/chromium.org/file/d/0BxrfNSu8yqJqUkNUVlFlYW5Uckk/view?usp=sharing

Bug: 622743
Change-Id: Icf8072513c202f9a0909d246c1476e2e2a55a705
Reviewed-on: https://chromium-review.googlesource.com/594052
Commit-Queue: Elodie Banel <lod@chromium.org>
Reviewed-by: default avatarGauthier Ambard <gambard@chromium.org>
Reviewed-by: default avatarMark Cogan <marq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#491678}
parent e10c7679
...@@ -27,7 +27,7 @@ source_set("favicon_ui") { ...@@ -27,7 +27,7 @@ source_set("favicon_ui") {
] ]
deps = [ deps = [
"//base", "//base",
"//ios/chrome/browser/ui", "//ios/chrome/browser/ui/util:constraints_ui",
] ]
configs += [ "//build/config/compiler:enable_arc" ] configs += [ "//build/config/compiler:enable_arc" ]
} }
...@@ -153,6 +153,9 @@ void WriteSavedMostVisited(NSDictionary<NSURL*, NTPTile*>* mostVisitedSites) { ...@@ -153,6 +153,9 @@ void WriteSavedMostVisited(NSDictionary<NSURL*, NTPTile*>* mostVisitedSites) {
NSUserDefaults* sharedDefaults = NSUserDefaults* sharedDefaults =
[[NSUserDefaults alloc] initWithSuiteName:app_group::ApplicationGroup()]; [[NSUserDefaults alloc] initWithSuiteName:app_group::ApplicationGroup()];
[sharedDefaults setObject:data forKey:app_group::kSuggestedItems]; [sharedDefaults setObject:data forKey:app_group::kSuggestedItems];
// TODO(crbug.com/750673): Update the widget's visibility depending on
// availability of sites.
} }
NSDictionary* ReadSavedMostVisited() { NSDictionary* ReadSavedMostVisited() {
......
...@@ -44,11 +44,15 @@ source_set("content_widget") { ...@@ -44,11 +44,15 @@ source_set("content_widget") {
"content_widget_view.mm", "content_widget_view.mm",
"content_widget_view_controller.h", "content_widget_view_controller.h",
"content_widget_view_controller.mm", "content_widget_view_controller.mm",
"most_visited_tile_view.h",
"most_visited_tile_view.mm",
] ]
deps = [ deps = [
"//base", "//base",
"//ios/chrome/browser/ui/favicon:favicon_ui",
"//ios/chrome/browser/ui/ntp:ntp_tile", "//ios/chrome/browser/ui/ntp:ntp_tile",
"//ios/chrome/browser/ui/util:constraints_ui",
"//ios/chrome/common/app_group", "//ios/chrome/common/app_group",
] ]
......
include_rules = [ include_rules = [
"-url", "-url",
"+ios/chrome/browser/ui/util",
"+ios/chrome/browser/ui/favicon",
"+ios/chrome/browser/ui/ntp", "+ios/chrome/browser/ui/ntp",
] ]
...@@ -7,7 +7,30 @@ ...@@ -7,7 +7,30 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
// View for the content widget. Shows 1 (compact view) or 2 (full size view)
// rows of 4 most visited tiles (favicon or fallback + title), if there are
// enough tiles to show. If there are fewer than 4 tiles, always displays a
// single row.
@interface ContentWidgetView : UIView @interface ContentWidgetView : UIView
// The height of the widget in expanded mode.
@property(nonatomic, readonly) CGFloat widgetExpandedHeight;
// Designated initializer, creates the widget view. |compactHeight| indicates
// the size to use in compact display. |initiallyCompact| indicates which mode
// to display on initialization.
- (instancetype)initWithCompactHeight:(CGFloat)compactHeight
initiallyCompact:(BOOL)compact NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
// Updates the view to display a compact or expanded view, depending on
// |compact|. If |compact| is false, the view shows a maximum of 8 tiles. If
// |compact| is true, the view is set to show a single row of 4 tiles at most
// within the |compactHeight| passed in the constructor.
- (void)showMode:(BOOL)compact;
@end @end
#endif // IOS_CHROME_CONTENT_WIDGET_EXTENSION_CONTENT_WIDGET_VIEW_H_ #endif // IOS_CHROME_CONTENT_WIDGET_EXTENSION_CONTENT_WIDGET_VIEW_H_
...@@ -4,12 +4,159 @@ ...@@ -4,12 +4,159 @@
#import "ios/chrome/content_widget_extension/content_widget_view.h" #import "ios/chrome/content_widget_extension/content_widget_view.h"
#include "base/ios/ios_util.h" #import "ios/chrome/browser/ui/favicon/favicon_view.h"
#include "base/logging.h" #import "ios/chrome/browser/ui/util/constraints_ui_util.h"
#import "ios/chrome/content_widget_extension/most_visited_tile_view.h"
#if !defined(__has_feature) || !__has_feature(objc_arc) #if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support." #error "This file requires ARC support."
#endif #endif
namespace {
// Spacing between tiles.
const CGFloat kTileSpacing = 16;
// Height of a tile row.
const CGFloat kTileHeight = 100;
// Icons to show per row.
const int kIconsPerRow = 4;
}
@interface ContentWidgetView ()
// The first row of sites.
@property(nonatomic, strong) UIView* firstRow;
// The second row of sites.
@property(nonatomic, strong) UIView* secondRow;
// The height used in the compact display mode.
@property(nonatomic) CGFloat compactHeight;
// The first row's height constraint. Set its constant to modify the first row's
// height.
@property(nonatomic, strong) NSLayoutConstraint* firstRowHeightConstraint;
// Whether the second row of sites should be shown. False if there are no sites
// to show in that row.
@property(nonatomic, readonly) BOOL shouldShowSecondRow;
// The number of sites to display.
@property(nonatomic, assign) int siteCount;
// Sets up the widget UI for an expanded or compact appearance based on
// |compact|.
- (void)createUI:(BOOL)compact;
// Creates the view for a row of 4 sites.
- (UIView*)createRowOfSites;
// Returns the height to use for the first row, depending on the display mode.
- (CGFloat)firstRowHeight:(BOOL)compact;
// Returns the height to use for the second row (can be 0 if the row should not
// be shown).
- (CGFloat)secondRowHeight;
@end
@implementation ContentWidgetView @implementation ContentWidgetView
@synthesize firstRow = _firstRow;
@synthesize secondRow = _secondRow;
@synthesize compactHeight = _compactHeight;
@synthesize firstRowHeightConstraint = _firstRowHeightConstraint;
@synthesize siteCount = _siteCount;
- (instancetype)initWithCompactHeight:(CGFloat)compactHeight
initiallyCompact:(BOOL)compact {
self = [super initWithFrame:CGRectZero];
if (self) {
_compactHeight = compactHeight;
[self createUI:compact];
}
return self;
}
#pragma mark - properties
- (CGFloat)widgetExpandedHeight {
return [self firstRowHeight:NO] + [self secondRowHeight];
}
- (BOOL)shouldShowSecondRow {
return self.siteCount > kIconsPerRow;
}
#pragma mark - UI creation
- (void)createUI:(BOOL)compact {
_firstRow = [self createRowOfSites];
_secondRow = [self createRowOfSites];
[self addSubview:_firstRow];
[self addSubview:_secondRow];
_firstRowHeightConstraint = [_firstRow.heightAnchor
constraintEqualToConstant:[self firstRowHeight:compact]];
[NSLayoutConstraint activateConstraints:@[
[_firstRow.topAnchor constraintEqualToAnchor:self.topAnchor],
[_secondRow.topAnchor constraintEqualToAnchor:_firstRow.bottomAnchor],
[self.leadingAnchor constraintEqualToAnchor:_firstRow.leadingAnchor],
[self.leadingAnchor constraintEqualToAnchor:_secondRow.leadingAnchor],
[self.trailingAnchor constraintEqualToAnchor:_firstRow.trailingAnchor],
[self.trailingAnchor constraintEqualToAnchor:_secondRow.trailingAnchor],
_firstRowHeightConstraint,
]];
}
- (UIView*)createRowOfSites {
NSMutableArray<MostVisitedTileView*>* cells = [[NSMutableArray alloc] init];
for (int i = 0; i < kIconsPerRow; i++) {
cells[i] = [[MostVisitedTileView alloc] init];
cells[i].translatesAutoresizingMaskIntoConstraints = NO;
}
UIStackView* stack = [[UIStackView alloc] initWithArrangedSubviews:cells];
stack.translatesAutoresizingMaskIntoConstraints = NO;
stack.axis = UILayoutConstraintAxisHorizontal;
stack.alignment = UIStackViewAlignmentTop;
stack.distribution = UIStackViewDistributionEqualSpacing;
stack.layoutMargins = UIEdgeInsetsZero;
stack.spacing = kTileSpacing;
stack.layoutMarginsRelativeArrangement = YES;
UIView* container = [[UIView alloc] initWithFrame:CGRectZero];
container.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:stack];
[NSLayoutConstraint activateConstraints:@[
[stack.centerYAnchor constraintEqualToAnchor:container.centerYAnchor],
[stack.centerXAnchor constraintEqualToAnchor:container.centerXAnchor],
[container.heightAnchor constraintGreaterThanOrEqualToConstant:kTileHeight],
]];
return container;
}
- (CGFloat)firstRowHeight:(BOOL)compact {
if (compact) {
return self.compactHeight;
}
CGFloat firstRowHeight = kTileHeight + 2 * kTileSpacing;
CGFloat secondRowHeight = [self secondRowHeight];
CGFloat totalHeight = firstRowHeight + secondRowHeight;
if (totalHeight >= self.compactHeight) {
return firstRowHeight;
}
return self.compactHeight - secondRowHeight;
}
- (CGFloat)secondRowHeight {
return self.shouldShowSecondRow ? kTileHeight + kTileSpacing : 0;
}
#pragma mark - ContentWidgetView
- (void)showMode:(BOOL)compact {
self.firstRowHeightConstraint.constant = [self firstRowHeight:compact];
}
@end @end
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/mac/foundation_util.h" #include "base/mac/foundation_util.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#import "ios/chrome/browser/ui/ntp/ntp_tile.h" #import "ios/chrome/browser/ui/ntp/ntp_tile.h"
#import "ios/chrome/browser/ui/util/constraints_ui_util.h"
#include "ios/chrome/common/app_group/app_group_constants.h" #include "ios/chrome/common/app_group/app_group_constants.h"
#include "ios/chrome/content_widget_extension/content_widget_view.h" #include "ios/chrome/content_widget_extension/content_widget_view.h"
...@@ -23,11 +24,13 @@ namespace { ...@@ -23,11 +24,13 @@ namespace {
// cannot be used. This class makes a very basic use of x-callback-url, so no // cannot be used. This class makes a very basic use of x-callback-url, so no
// full implementation is required. // full implementation is required.
NSString* const kXCallbackURLHost = @"x-callback-url"; NSString* const kXCallbackURLHost = @"x-callback-url";
const CGFloat widgetCompactHeightIOS9 = 110;
} // namespace } // namespace
@interface ContentWidgetViewController () @interface ContentWidgetViewController ()
@property(nonatomic, strong) NSDictionary<NSURL*, NTPTile*>* sites;
@property(nonatomic, weak) ContentWidgetView* widgetView; @property(nonatomic, weak) ContentWidgetView* widgetView;
@property(nonatomic, strong) NSArray<NTPTile*>* sites; @property(nonatomic, readonly) BOOL isCompact;
// Updates the widget with latest data. Returns whether any visual updates // Updates the widget with latest data. Returns whether any visual updates
// occurred. // occurred.
...@@ -41,16 +44,11 @@ NSString* const kXCallbackURLHost = @"x-callback-url"; ...@@ -41,16 +44,11 @@ NSString* const kXCallbackURLHost = @"x-callback-url";
@synthesize sites = _sites; @synthesize sites = _sites;
@synthesize widgetView = _widgetView; @synthesize widgetView = _widgetView;
- (instancetype)init { #pragma mark - properties
self = [super init];
if (self) { - (BOOL)isCompact {
NSUserDefaults* sharedDefaults = [[NSUserDefaults alloc] return [self.extensionContext widgetActiveDisplayMode] ==
initWithSuiteName:app_group::ApplicationGroup()]; NCWidgetDisplayModeCompact;
_sites = [NSKeyedUnarchiver
unarchiveObjectWithData:[sharedDefaults
objectForKey:app_group::kSuggestedItems]];
}
return self;
} }
#pragma mark - UIViewController #pragma mark - UIViewController
...@@ -58,9 +56,18 @@ NSString* const kXCallbackURLHost = @"x-callback-url"; ...@@ -58,9 +56,18 @@ NSString* const kXCallbackURLHost = @"x-callback-url";
- (void)viewDidLoad { - (void)viewDidLoad {
[super viewDidLoad]; [super viewDidLoad];
CGFloat height =
self.extensionContext
? [self.extensionContext
widgetMaximumSizeForDisplayMode:NCWidgetDisplayModeCompact]
.height
: widgetCompactHeightIOS9;
// A local variable is necessary here as the property is declared weak and the // A local variable is necessary here as the property is declared weak and the
// object would be deallocated before being retained by the addSubview call. // object would be deallocated before being retained by the addSubview call.
ContentWidgetView* widgetView = [[ContentWidgetView alloc] init]; ContentWidgetView* widgetView =
[[ContentWidgetView alloc] initWithCompactHeight:height
initiallyCompact:self.isCompact];
self.widgetView = widgetView; self.widgetView = widgetView;
[self.view addSubview:self.widgetView]; [self.view addSubview:self.widgetView];
...@@ -70,15 +77,7 @@ NSString* const kXCallbackURLHost = @"x-callback-url"; ...@@ -70,15 +77,7 @@ NSString* const kXCallbackURLHost = @"x-callback-url";
} }
self.widgetView.translatesAutoresizingMaskIntoConstraints = NO; self.widgetView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[ AddSameConstraints(self.widgetView, self.view);
[self.view.leadingAnchor
constraintEqualToAnchor:self.widgetView.leadingAnchor],
[self.view.trailingAnchor
constraintEqualToAnchor:self.widgetView.trailingAnchor],
[self.view.topAnchor constraintEqualToAnchor:self.widgetView.topAnchor],
[self.view.bottomAnchor
constraintEqualToAnchor:self.widgetView.bottomAnchor]
]];
} }
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
...@@ -92,29 +91,32 @@ NSString* const kXCallbackURLHost = @"x-callback-url"; ...@@ -92,29 +91,32 @@ NSString* const kXCallbackURLHost = @"x-callback-url";
: NCUpdateResultNoData); : NCUpdateResultNoData);
} }
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:
(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator
animateAlongsideTransition:^(
id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[self.widgetView showMode:self.isCompact];
}
completion:nil];
}
#pragma mark - NCWidgetProviding #pragma mark - NCWidgetProviding
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode - (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode
withMaximumSize:(CGSize)maxSize { withMaximumSize:(CGSize)maxSize {
BOOL isVariableHeight = (activeDisplayMode == NCWidgetDisplayModeExpanded); switch (activeDisplayMode) {
case NCWidgetDisplayModeCompact:
// If the widget's height is not variable, the preferredContentSize is the
// maxSize. Widgets cannot be shrunk, and this ensures the view will lay
// itself out according to the actual screen size. (This is only likely to
// happen if the accessibility option for larger font is used.) If the widget
// is not a fixed size, if the fitting size for the widget's contents is
// larger than the maximum size for the current widget display mode, this
// maximum size is used for the widget. Otherwise, the preferredContentSize is
// set to the fitting size so that the widget gets the correct height.
if (isVariableHeight) {
CGSize fittingSize = [self.widgetView
systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
if (fittingSize.height < maxSize.height) {
self.preferredContentSize = fittingSize;
return;
}
}
self.preferredContentSize = maxSize; self.preferredContentSize = maxSize;
break;
case NCWidgetDisplayModeExpanded:
self.preferredContentSize =
CGSizeMake(maxSize.width, [self.widgetView widgetExpandedHeight]);
break;
}
} }
// Implementing this method removes the leading edge inset for iOS version < 10. // Implementing this method removes the leading edge inset for iOS version < 10.
...@@ -131,13 +133,13 @@ NSString* const kXCallbackURLHost = @"x-callback-url"; ...@@ -131,13 +133,13 @@ NSString* const kXCallbackURLHost = @"x-callback-url";
- (BOOL)updateWidget { - (BOOL)updateWidget {
NSUserDefaults* sharedDefaults = NSUserDefaults* sharedDefaults =
[[NSUserDefaults alloc] initWithSuiteName:app_group::ApplicationGroup()]; [[NSUserDefaults alloc] initWithSuiteName:app_group::ApplicationGroup()];
NSMutableArray<NTPTile*>* newSites = [NSKeyedUnarchiver NSDictionary<NSURL*, NTPTile*>* newSites = [NSKeyedUnarchiver
unarchiveObjectWithData:[sharedDefaults unarchiveObjectWithData:[sharedDefaults
objectForKey:app_group::kSuggestedItems]]; objectForKey:app_group::kSuggestedItems]];
if (newSites == self.sites) { if (newSites == self.sites) {
return NO; return NO;
} }
self.sites = newSites;
return YES; return YES;
} }
......
// Copyright 2017 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.
#ifndef IOS_CHROME_CONTENT_WIDGET_EXTENSION_MOST_VISITED_TILE_VIEW_H_
#define IOS_CHROME_CONTENT_WIDGET_EXTENSION_MOST_VISITED_TILE_VIEW_H_
#import <UIKit/UIKit.h>
@class FaviconViewNew;
// View to display a Most Visited tile based on the suggestion.
// It displays the favicon for this Most Visited suggestion and its title.
@interface MostVisitedTileView : UIView
// FaviconView displaying the favicon.
@property(nonatomic, strong, readonly, nonnull) FaviconViewNew* faviconView;
// Title of the Most Visited.
@property(nonatomic, strong, readonly, nonnull) UILabel* titleLabel;
@end
#endif // IOS_CHROME_CONTENT_WIDGET_EXTENSION_MOST_VISITED_TILE_VIEW_H_
// Copyright 2017 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/content_widget_extension/most_visited_tile_view.h"
#import "ios/chrome/browser/ui/favicon/favicon_view.h"
#import "ios/chrome/browser/ui/util/constraints_ui_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
const CGFloat kLabelTextColor = 0.314;
const NSInteger kLabelNumLines = 2;
const CGFloat kFaviconSize = 48;
const CGFloat kSpaceFaviconTitle = 8;
// Width of a tile.
const CGFloat kTileWidth = 73;
}
@implementation MostVisitedTileView
@synthesize faviconView = _faviconView;
@synthesize titleLabel = _titleLabel;
#pragma mark - Public
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
_titleLabel.textColor = [UIColor colorWithWhite:kLabelTextColor alpha:1.0];
_titleLabel.font =
[UIFont preferredFontForTextStyle:UIFontTextStyleFootnote];
_titleLabel.textAlignment = NSTextAlignmentCenter;
_titleLabel.isAccessibilityElement = NO;
_titleLabel.numberOfLines = kLabelNumLines;
_faviconView = [[FaviconViewNew alloc] init];
_faviconView.isAccessibilityElement = NO;
_faviconView.font =
[UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
UIStackView* stack = [[UIStackView alloc]
initWithArrangedSubviews:@[ _faviconView, _titleLabel ]];
stack.axis = UILayoutConstraintAxisVertical;
stack.spacing = kSpaceFaviconTitle;
stack.alignment = UIStackViewAlignmentCenter;
stack.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:stack];
AddSameConstraints(self, stack);
[NSLayoutConstraint activateConstraints:@[
[stack.widthAnchor constraintEqualToConstant:kTileWidth],
[_faviconView.widthAnchor constraintEqualToConstant:kFaviconSize],
[_faviconView.heightAnchor constraintEqualToConstant:kFaviconSize],
]];
}
return self;
}
@end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment