Commit ceea172d authored by sdefresne's avatar sdefresne Committed by Commit bot

Upstream Chrome on iOS source code [7/11].

Upstream part of Chrome on iOS source code. Nothing is built yet,
just new files added. The files will be added to the build as part
of the last CL to avoid breaking downstream tree.

BUG=653086

Review-Url: https://codereview.chromium.org/2589583003
Cr-Commit-Position: refs/heads/master@{#439469}
parent 6bd0dace
This diff is collapsed.
// 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.
#ifndef IOS_CHROME_BROWSER_UI_READER_MODE_READER_MODE_CHECKER_H_
#define IOS_CHROME_BROWSER_UI_READER_MODE_READER_MODE_CHECKER_H_
#import <Foundation/Foundation.h>
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "ios/web/public/web_state/web_state_observer.h"
namespace web {
class WebState;
}
class ReaderModeChecker;
@class CRWJSInjectionReceiver;
class ReaderModeCheckerObserver {
public:
explicit ReaderModeCheckerObserver(ReaderModeChecker* readerModeChecker);
virtual ~ReaderModeCheckerObserver();
// Called when the content of the page is known to be distillable.
virtual void PageIsDistillable() = 0;
// Invoked when the ReaderModeChecker is being destroyed. Gives subclasses a
// chance to cleanup.
virtual void ReaderModeCheckerDestroyed() {}
private:
ReaderModeChecker* readerModeChecker_;
DISALLOW_COPY_AND_ASSIGN(ReaderModeCheckerObserver);
};
// Checks if a page loaded in a container that allows javascript execution is
// suitable for reader mode.
class ReaderModeChecker : web::WebStateObserver {
public:
explicit ReaderModeChecker(web::WebState* web_state);
~ReaderModeChecker() override;
// Returns the switch status of the current page.
bool CanSwitchToReaderMode();
// web::WebStateObserver.
void NavigationItemCommitted(
const web::LoadCommittedDetails& load_details) override;
void PageLoaded(
web::PageLoadCompletionStatus load_completion_status) override;
void WebStateDestroyed() override;
// Adds and removes observers for page navigation notifications. The order in
// which notifications are sent to observers is undefined. Clients must be
// sure to remove the observer before they go away.
void AddObserver(ReaderModeCheckerObserver* observer);
void RemoveObserver(ReaderModeCheckerObserver* observer);
private:
// Checks if the current webstate is distillable.
void CheckIsDistillable();
// Does a simple check, Just checking OpenGraph properties.
void CheckIsDistillableOG(CRWJSInjectionReceiver* receiver);
// Does a more involved check.
void CheckIsDistillableDetector(CRWJSInjectionReceiver* receiver);
// Stores if the current webstate URL is suitable for reader mode.
enum class Status;
Status is_distillable_;
// A list of observers notified when page state changes. Weak references.
base::ObserverList<ReaderModeCheckerObserver, true> observers_;
base::WeakPtrFactory<ReaderModeChecker> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(ReaderModeChecker);
};
#endif // IOS_CHROME_BROWSER_UI_READER_MODE_READER_MODE_CHECKER_H_
// 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/reader_mode/reader_mode_checker.h"
#include "base/logging.h"
#include "base/strings/sys_string_conversions.h"
#include "components/dom_distiller/core/distillable_page_detector.h"
#include "components/dom_distiller/core/experiments.h"
#include "components/dom_distiller/core/page_features.h"
#include "components/dom_distiller/core/url_utils.h"
#include "components/grit/components_resources.h"
#include "ios/web/public/web_state/js/crw_js_injection_evaluator.h"
#include "ios/web/public/web_state/web_state.h"
#include "ui/base/resource/resource_bundle.h"
ReaderModeCheckerObserver::ReaderModeCheckerObserver(
ReaderModeChecker* readerModeChecker)
: readerModeChecker_(readerModeChecker) {
DCHECK(readerModeChecker);
readerModeChecker_->AddObserver(this);
}
ReaderModeCheckerObserver::~ReaderModeCheckerObserver() {
readerModeChecker_->RemoveObserver(this);
}
enum class ReaderModeChecker::Status {
kSwitchNo, // There is no way a switch could happen.
kSwitchMaybe, // Not sure, it may be possible.
kSwitchPossible, // It is possible to switch to reader mode.
};
ReaderModeChecker::ReaderModeChecker(web::WebState* web_state)
: web::WebStateObserver(web_state),
is_distillable_(Status::kSwitchNo),
weak_factory_(this) {
DCHECK(web_state);
}
ReaderModeChecker::~ReaderModeChecker() {
for (auto& observer : observers_)
observer.ReaderModeCheckerDestroyed();
}
bool ReaderModeChecker::CanSwitchToReaderMode() {
return is_distillable_ != ReaderModeChecker::Status::kSwitchNo;
}
void ReaderModeChecker::PageLoaded(
web::PageLoadCompletionStatus load_completion_status) {
if (load_completion_status != web::PageLoadCompletionStatus::SUCCESS)
return;
if (is_distillable_ == Status::kSwitchPossible)
return;
CheckIsDistillable();
}
void ReaderModeChecker::NavigationItemCommitted(
const web::LoadCommittedDetails& load_details) {
// When NavigationItemCommitted the page may not be completely loaded. But in
// order to get reader mode data faster this class tries to inject the
// javascript early. If it fails, it will be retried at PageLoaded().
is_distillable_ = Status::kSwitchNo;
CheckIsDistillable();
}
void ReaderModeChecker::CheckIsDistillableOG(CRWJSInjectionReceiver* receiver) {
// Retrieve the javascript used to figure out if the page is at all
// distillable.
NSString* is_distillable_js =
base::SysUTF8ToNSString(dom_distiller::url_utils::GetIsDistillableJs());
DCHECK(is_distillable_js);
// In case |this| gets deallocated before the block executes, the block only
// references a weak pointer.
base::WeakPtr<ReaderModeChecker> weak_this(weak_factory_.GetWeakPtr());
[receiver executeJavaScript:is_distillable_js
completionHandler:^(id result, NSError* error) {
if (!weak_this || error || !result)
return; // Inconclusive.
if ([result isEqual:@YES]) {
weak_this->is_distillable_ = Status::kSwitchPossible;
for (auto& observer : weak_this->observers_)
observer.PageIsDistillable();
} else {
weak_this->is_distillable_ = Status::kSwitchMaybe;
}
}];
}
void ReaderModeChecker::CheckIsDistillableDetector(
CRWJSInjectionReceiver* receiver) {
// Retrieve the javascript used to figure out if the page is at all
// distillable.
NSString* extract_features_js = base::SysUTF8ToNSString(
ResourceBundle::GetSharedInstance()
.GetRawDataResource(IDR_EXTRACT_PAGE_FEATURES_JS)
.as_string());
DCHECK(extract_features_js);
// In case |this| gets deallocated before the block executes, the block only
// references a weak pointer.
base::WeakPtr<ReaderModeChecker> weak_this(weak_factory_.GetWeakPtr());
[receiver
executeJavaScript:extract_features_js
completionHandler:^(id result, NSError* error) {
if (!weak_this || error || ![result isKindOfClass:[NSString class]]) {
return; // Inconclusive.
}
const dom_distiller::DistillablePageDetector* detector =
dom_distiller::DistillablePageDetector::GetDefault();
const base::StringValue value(base::SysNSStringToUTF8(result));
std::vector<double> features(
dom_distiller::CalculateDerivedFeaturesFromJSON(&value));
if (detector->Classify(features)) {
weak_this->is_distillable_ = Status::kSwitchPossible;
for (auto& observer : weak_this->observers_)
observer.PageIsDistillable();
} else {
weak_this->is_distillable_ = Status::kSwitchMaybe;
}
}];
}
void ReaderModeChecker::CheckIsDistillable() {
CRWJSInjectionReceiver* receiver = web_state()->GetJSInjectionReceiver();
if (!receiver || !web_state()->ContentIsHTML() ||
web_state()->IsShowingWebInterstitial()) {
is_distillable_ = Status::kSwitchNo;
return;
}
switch (dom_distiller::GetDistillerHeuristicsType()) {
case dom_distiller::DistillerHeuristicsType::NONE:
is_distillable_ = Status::kSwitchMaybe;
break;
case dom_distiller::DistillerHeuristicsType::OG_ARTICLE:
CheckIsDistillableOG(receiver);
break;
case dom_distiller::DistillerHeuristicsType::ADABOOST_MODEL:
CheckIsDistillableDetector(receiver);
break;
case dom_distiller::DistillerHeuristicsType::ALWAYS_TRUE:
is_distillable_ = Status::kSwitchPossible;
break;
}
}
void ReaderModeChecker::WebStateDestroyed() {
is_distillable_ = Status::kSwitchNo;
}
void ReaderModeChecker::AddObserver(ReaderModeCheckerObserver* observer) {
DCHECK(!observers_.HasObserver(observer));
observers_.AddObserver(observer);
}
void ReaderModeChecker::RemoveObserver(ReaderModeCheckerObserver* observer) {
DCHECK(observers_.HasObserver(observer));
observers_.RemoveObserver(observer);
}
// 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.
#ifndef IOS_CHROME_BROWSER_UI_READER_MODE_READER_MODE_CONTROLLER_H_
#define IOS_CHROME_BROWSER_UI_READER_MODE_READER_MODE_CONTROLLER_H_
#import <UIKit/UIKit.h>
class GURL;
class ReaderModeChecker;
namespace web {
class WebState;
}
@protocol ReaderModeControllerDelegate
// Called by the controller to retrieve the superview that the reader mode
// panel should be a subview of. The panel's position is based on the size of
// the superview, which is presumed to fill the device's screen.
- (UIView*)superviewForReaderModePanel;
// Called when the distillation finishes.
- (void)loadReaderModeHTML:(NSString*)html forURL:(const GURL&)url;
@end
// A reader mode controller can trigger distillations on the current visible
// page on a web state.
@interface ReaderModeController : NSObject
- (instancetype)initWithWebState:(web::WebState*)state
delegate:(id<ReaderModeControllerDelegate>)delegate
NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
// The object that checks if a URL is suitable for reader mode.
@property(nonatomic, readonly, assign) ReaderModeChecker* checker;
// Starts a distillation, animates a view on screen to show a waiting UI to the
// user while the distillation is ongoing, and calls the delegate once finished.
- (void)switchToReaderMode;
// Removes the waiting view from the superview returned by the delegate.
- (void)exitReaderMode;
// Instructs the controller to detach itself from the web state.
- (void)detachFromWebState;
@end
#endif // IOS_CHROME_BROWSER_UI_READER_MODE_READER_MODE_CONTROLLER_H_
// 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/reader_mode/reader_mode_controller.h"
#include <memory>
#include <utility>
#include "base/ios/weak_nsobject.h"
#include "base/mac/bind_objc_block.h"
#include "base/mac/scoped_nsobject.h"
#include "base/memory/ptr_util.h"
#include "base/strings/sys_string_conversions.h"
#include "components/infobars/core/infobar.h"
#include "components/infobars/core/infobar_manager.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/dom_distiller/distiller_viewer.h"
#include "ios/chrome/browser/dom_distiller/dom_distiller_service_factory.h"
#include "ios/chrome/browser/infobars/infobar_manager_impl.h"
#import "ios/chrome/browser/ui/reader_mode/reader_mode_checker.h"
#import "ios/chrome/browser/ui/reader_mode/reader_mode_infobar_delegate.h"
#import "ios/chrome/browser/ui/reader_mode/reader_mode_view.h"
#include "ios/web/public/browser_state.h"
#include "ios/web/public/web_state/web_state.h"
@protocol ReaderModeCheckerObserverBridgeProtocol
- (void)pageIsDistillable;
@end
@protocol InfoBarManagerObserverBridgeProtocol
- (void)infoBarRemoved:(infobars::InfoBar*)infobar;
@end
namespace {
// Used to find the ReaderModeView in the view hierarchy.
const NSInteger kReaderModeViewTag = 42;
const CGFloat kReaderModeAnimationDuration = .5;
class ReaderModeCheckerObserverBridge : ReaderModeCheckerObserver {
public:
ReaderModeCheckerObserverBridge(
ReaderModeChecker* readerModeChecker,
id<ReaderModeCheckerObserverBridgeProtocol> observer)
: ReaderModeCheckerObserver(readerModeChecker), observer_(observer) {
DCHECK(observer);
};
void PageIsDistillable() override { [observer_ pageIsDistillable]; };
private:
id<ReaderModeCheckerObserverBridgeProtocol> observer_;
};
class InfoBarManagerObserverBridge : infobars::InfoBarManager::Observer {
public:
InfoBarManagerObserverBridge(
infobars::InfoBarManager* infoBarManager,
id<InfoBarManagerObserverBridgeProtocol> observer)
: infobars::InfoBarManager::Observer(),
manager_(infoBarManager),
observer_(observer) {
DCHECK(infoBarManager);
DCHECK(observer);
manager_->AddObserver(this);
};
~InfoBarManagerObserverBridge() override {
if (manager_)
manager_->RemoveObserver(this);
}
void OnInfoBarRemoved(infobars::InfoBar* infobar, bool animate) override {
[observer_ infoBarRemoved:infobar];
}
void OnManagerShuttingDown(infobars::InfoBarManager* manager) override {
manager_->RemoveObserver(this);
manager_ = nullptr;
}
private:
infobars::InfoBarManager* manager_;
id<InfoBarManagerObserverBridgeProtocol> observer_;
};
} // namespace
@interface ReaderModeController ()<ReaderModeViewDelegate,
ReaderModeCheckerObserverBridgeProtocol,
InfoBarManagerObserverBridgeProtocol> {
std::unique_ptr<ReaderModeChecker> _checker;
std::unique_ptr<ReaderModeCheckerObserverBridge> _checkerBridge;
std::unique_ptr<InfoBarManagerObserverBridge> _infoBarBridge;
std::unique_ptr<dom_distiller::DistillerViewer> _viewer;
// The currently displayed infobar.
infobars::InfoBar* infobar_;
web::WebState* _webState;
}
@property(readonly, nonatomic) id<ReaderModeControllerDelegate> delegate;
// Triggers a distillation and returns a DistillerViewer to keep as a handle to
// the running distillation.
- (std::unique_ptr<dom_distiller::DistillerViewer>)startDistillation
WARN_UNUSED_RESULT;
- (void)distillationFinished:(const std::string&)html forURL:(const GURL&)url;
// Returns a ReaderModeView that presents a waiting UI while the distillation
// is taking place. Releasing this view will stop the distillation in progress.
- (ReaderModeView*)readerModeViewWithFrame:(CGRect)frame;
- (void)showInfoBar:(const std::string&)html forURL:(const GURL&)url;
- (void)removeInfoBar;
@end
@implementation ReaderModeController
@synthesize delegate = _delegate;
- (instancetype)initWithWebState:(web::WebState*)webState
delegate:(id<ReaderModeControllerDelegate>)delegate {
DCHECK(webState);
self = [super init];
if (self) {
_webState = webState;
_checker = base::MakeUnique<ReaderModeChecker>(_webState);
_delegate = delegate;
_checkerBridge =
base::MakeUnique<ReaderModeCheckerObserverBridge>(_checker.get(), self);
infobars::InfoBarManager* infobar_manager =
InfoBarManagerImpl::FromWebState(_webState);
_infoBarBridge =
base::MakeUnique<InfoBarManagerObserverBridge>(infobar_manager, self);
}
return self;
}
- (instancetype)init {
NOTREACHED();
return nil;
}
- (void)dealloc {
if (_webState)
[self detachFromWebState];
[super dealloc];
}
- (void)detachFromWebState {
[self removeInfoBar];
_webState = nullptr;
}
// Property accessor.
- (ReaderModeChecker*)checker {
return _checker.get();
}
#pragma mark - Private methods.
#pragma mark distillation
- (std::unique_ptr<dom_distiller::DistillerViewer>)startDistillation {
DCHECK(_webState);
base::WeakNSObject<ReaderModeController> weakSelf(self);
GURL pageURL = _webState->GetLastCommittedURL();
ios::ChromeBrowserState* browserState =
ios::ChromeBrowserState::FromBrowserState(_webState->GetBrowserState());
return base::MakeUnique<dom_distiller::DistillerViewer>(
dom_distiller::DomDistillerServiceFactory::GetForBrowserState(
browserState),
browserState->GetPrefs(), pageURL,
base::BindBlock(^(
const GURL& pageURL, const std::string& html,
const std::vector<dom_distiller::DistillerViewer::ImageInfo>& images,
const std::string& title) {
[weakSelf distillationFinished:html forURL:pageURL];
}));
}
- (void)distillationFinished:(const std::string&)html forURL:(const GURL&)url {
UIView* superview = [self.delegate superviewForReaderModePanel];
DCHECK(_viewer || [superview viewWithTag:kReaderModeViewTag]);
if ([superview viewWithTag:kReaderModeViewTag]) {
[self.delegate loadReaderModeHTML:base::SysUTF8ToNSString(html) forURL:url];
} else if (_viewer) {
[self showInfoBar:html forURL:url];
}
}
#pragma mark view creation
- (ReaderModeView*)readerModeViewWithFrame:(CGRect)frame {
DCHECK(_checker->CanSwitchToReaderMode());
ReaderModeView* view =
[[[ReaderModeView alloc] initWithFrame:frame delegate:self] autorelease];
[view assignDistillerViewer:[self startDistillation]];
return view;
}
#pragma mark infobar.
- (void)showInfoBar:(const std::string&)html forURL:(const GURL&)url {
DCHECK(_webState);
base::WeakNSProtocol<id<ReaderModeControllerDelegate>> weakDelegate(
self.delegate);
// Non reference version of the variables needed.
const std::string html_non_ref(html);
const GURL url_non_ref(url);
auto infoBarDelegate =
base::MakeUnique<ReaderModeInfoBarDelegate>(base::BindBlock(^{
[weakDelegate loadReaderModeHTML:base::SysUTF8ToNSString(html_non_ref)
forURL:url_non_ref];
}));
infobars::InfoBarManager* infobar_manager =
InfoBarManagerImpl::FromWebState(_webState);
std::unique_ptr<infobars::InfoBar> infobar =
infobar_manager->CreateConfirmInfoBar(std::move(infoBarDelegate));
if (infobar_)
infobar_ = infobar_manager->ReplaceInfoBar(infobar_, std::move(infobar));
else
infobar_ = infobar_manager->AddInfoBar(std::move(infobar));
}
- (void)removeInfoBar {
DCHECK(_webState);
if (infobar_) {
infobars::InfoBarManager* infobar_manager =
InfoBarManagerImpl::FromWebState(_webState);
infobar_manager->RemoveInfoBar(infobar_);
infobar_ = nullptr;
}
}
#pragma mark - public methods.
- (void)switchToReaderMode {
UIView* superview = [self.delegate superviewForReaderModePanel];
if ([superview viewWithTag:kReaderModeViewTag])
return; // There is already a reader mode waiting view visible.
[self removeInfoBar];
// Get the view.
ReaderModeView* readerView = [self readerModeViewWithFrame:superview.bounds];
readerView.tag = kReaderModeViewTag;
[superview addSubview:readerView];
// Animate the view in. First the view is animated in (via transparency) and
// the the animation on the view itself is started.
readerView.alpha = 0.0;
[UIView animateWithDuration:kReaderModeAnimationDuration
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
readerView.alpha = 1.0;
}
completion:^(BOOL finished) {
if (finished)
[readerView start]; // Starts the waiting animation on the view.
}];
}
#pragma mark - ReaderModeViewDelegate.
- (void)exitReaderMode {
UIView* superview = [self.delegate superviewForReaderModePanel];
if (![superview viewWithTag:kReaderModeViewTag]) {
DCHECK(_viewer);
return;
}
ReaderModeView* readerView =
static_cast<ReaderModeView*>([superview viewWithTag:kReaderModeViewTag]);
if (!readerView)
return;
// First stop the view waiting animation (if any) and then remove the view.
[readerView stopWaitingWithCompletion:^{
[UIView animateWithDuration:kReaderModeAnimationDuration
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
readerView.alpha = 0.0;
}
completion:^(BOOL finished) {
[readerView removeFromSuperview];
}];
}];
}
#pragma mark - ReaderModeCheckerObserverBridgeProtocol
- (void)pageIsDistillable {
_viewer = [self startDistillation];
}
#pragma mark - InfoBarManagerObserverBridgeProtocol.
- (void)infoBarRemoved:(infobars::InfoBar*)infobar {
if (infobar == infobar_) {
_viewer.reset();
infobar_ = nullptr;
}
}
@end
// 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.
#ifndef IOS_CHROME_BROWSER_UI_READER_MODE_READER_MODE_INFOBAR_DELEGATE_H_
#define IOS_CHROME_BROWSER_UI_READER_MODE_READER_MODE_INFOBAR_DELEGATE_H_
#include <string>
#include "base/callback.h"
#include "components/infobars/core/confirm_infobar_delegate.h"
#include "url/gurl.h"
// This is the actual infobar displayed to prompt the user to switch to reader
// mode.
class ReaderModeInfoBarDelegate : public ConfirmInfoBarDelegate {
public:
explicit ReaderModeInfoBarDelegate(const base::Closure& callback);
~ReaderModeInfoBarDelegate() override;
// InfoBarDelegate methods.
InfoBarIdentifier GetIdentifier() const override;
// ConfirmInfoBarDelegate methods.
base::string16 GetMessageText() const override;
bool Accept() override;
private:
std::string html_;
GURL url_;
base::Closure callback_;
};
#endif // IOS_CHROME_BROWSER_UI_READER_MODE_READER_MODE_INFOBAR_DELEGATE_H_
// 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/reader_mode/reader_mode_infobar_delegate.h"
#include "base/strings/utf_string_conversions.h"
ReaderModeInfoBarDelegate::ReaderModeInfoBarDelegate(
const base::Closure& callback)
: callback_(callback) {}
ReaderModeInfoBarDelegate::~ReaderModeInfoBarDelegate() {}
infobars::InfoBarDelegate::InfoBarIdentifier
ReaderModeInfoBarDelegate::GetIdentifier() const {
return READER_MODE_INFOBAR_DELEGATE_IOS;
}
base::string16 ReaderModeInfoBarDelegate::GetMessageText() const {
// TODO(noyau): Waiting for text to put here.
return base::ASCIIToUTF16("Open in reader mode");
}
bool ReaderModeInfoBarDelegate::Accept() {
callback_.Run();
return true;
}
// 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.
#ifndef IOS_CHROME_BROWSER_UI_READER_MODE_READER_MODE_VIEW_H_
#define IOS_CHROME_BROWSER_UI_READER_MODE_READER_MODE_VIEW_H_
#import <UIKit/UIKit.h>
#include <memory>
#include "base/ios/block_types.h"
namespace dom_distiller {
class DistillerViewer;
}
// Simple delegate to be notified on user action.
@protocol ReaderModeViewDelegate
// Called when the user decides to exit reader mode.
- (void)exitReaderMode;
@end
// A waiting view to show to the user while the distillation is taking place.
// This view also owns the dom_distiller::DistillerViewer for the content, this
// allows for the distillation to stop if the view is deallocated before the
// distillation finishes.
@interface ReaderModeView : UIView
- (instancetype)initWithFrame:(CGRect)frame
delegate:(id<ReaderModeViewDelegate>)delegate
NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
// The view's delegate.
@property(nonatomic, readonly) id<ReaderModeViewDelegate> delegate;
// Takes ownership of the viewer as it is the only handle to the distillation:
// When freed it cancels the distillation, if it is not finished yet. The
// ReaderModeView instance owns this object so that the distillation is
// cancelled if the view is deallocated.
- (void)assignDistillerViewer:
(std::unique_ptr<dom_distiller::DistillerViewer>)viewer;
// Starts the waiting animation.
- (void)start;
// Call this method when this view is removed from the visible view hierarchy.
// |completion| will be called when this view is done animating out.
- (void)stopWaitingWithCompletion:(ProceduralBlock)completion;
@end
#endif // IOS_CHROME_BROWSER_UI_READER_MODE_READER_MODE_VIEW_H_
// 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/reader_mode/reader_mode_view.h"
#include "base/mac/objc_property_releaser.h"
#include "base/mac/scoped_nsobject.h"
#include "ios/chrome/browser/dom_distiller/distiller_viewer.h"
#import "ios/chrome/browser/ui/material_components/activity_indicator.h"
#import "ios/third_party/material_components_ios/src/components/ActivityIndicator/src/MaterialActivityIndicator.h"
namespace {
const CGFloat kCloseButtonSize = 40;
const CGFloat kCloseButtonMargin = 10;
const CGFloat kMinWidth = kCloseButtonSize + kCloseButtonMargin * 2;
const CGFloat kMinHeight = kMinWidth;
} // namespace
@interface ReaderModeView ()<MDCActivityIndicatorDelegate> {
std::unique_ptr<dom_distiller::DistillerViewer> _viewer;
base::mac::ObjCPropertyReleaser _propertyReleaser_ReaderModeView;
}
@property(nonatomic, retain) MDCActivityIndicator* activityIndicator;
@property(nonatomic, copy) ProceduralBlock animateOutCompletionBlock;
@property(nonatomic, retain) UIButton* closeButton;
@end
@implementation ReaderModeView
@synthesize activityIndicator = _activityIndicator;
@synthesize animateOutCompletionBlock = _animateOutCompletionBlock;
@synthesize closeButton = _closeButton;
@synthesize delegate = _delegate;
- (instancetype)initWithFrame:(CGRect)frame
delegate:(id<ReaderModeViewDelegate>)delegate {
self = [super initWithFrame:frame];
if (self) {
_propertyReleaser_ReaderModeView.Init(self, [ReaderModeView class]);
_delegate = delegate;
self.backgroundColor = [UIColor whiteColor];
self.autoresizingMask =
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_closeButton = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain];
[_closeButton addTarget:self
action:@selector(close)
forControlEvents:UIControlEventTouchUpInside];
[_closeButton setTitle:@"X" forState:UIControlStateNormal];
_closeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleTopMargin;
_activityIndicator =
[[MDCActivityIndicator alloc] initWithFrame:CGRectMake(0, 0, 24, 24)];
_activityIndicator.delegate = self;
_activityIndicator.cycleColors = ActivityIndicatorBrandedCycleColors();
_activityIndicator.autoresizingMask =
UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleRightMargin |
UIViewAutoresizingFlexibleBottomMargin;
[self addSubview:_closeButton];
[self addSubview:_activityIndicator];
[self sizeToFit];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
NOTREACHED();
return nil;
}
- (instancetype)initWithCoder:(NSCoder*)aDecoder {
NOTREACHED();
return nil;
}
- (void)start {
[self.activityIndicator startAnimating];
}
- (void)stopWaitingWithCompletion:(ProceduralBlock)completion {
if (self.activityIndicator.isAnimating) {
self.animateOutCompletionBlock = completion;
[self.activityIndicator stopAnimating];
} else if (completion) {
completion();
}
}
- (void)close {
[self.delegate exitReaderMode];
}
- (void)assignDistillerViewer:
(std::unique_ptr<dom_distiller::DistillerViewer>)viewer {
_viewer.reset(viewer.release());
}
- (void)layoutSubviews {
[super layoutSubviews];
self.closeButton.frame = CGRectMake(
self.bounds.size.width - kCloseButtonSize - kCloseButtonMargin,
self.bounds.size.height - kCloseButtonSize - kCloseButtonMargin,
kCloseButtonSize, kCloseButtonSize);
self.activityIndicator.center = CGPointMake(CGRectGetWidth(self.bounds) / 2,
CGRectGetHeight(self.bounds) / 2);
}
- (CGSize)sizeThatFits:(CGSize)size {
CGSize newSize = [super sizeThatFits:size];
if (newSize.width < kMinWidth)
newSize.width = kMinWidth;
if (newSize.height < kMinHeight)
newSize.height = kMinHeight;
return newSize;
}
#pragma mark - MDCActivityIndicatorDelegate
- (void)activityIndicatorAnimationDidFinish:
(MDCActivityIndicator*)activityIndicator {
[self.activityIndicator removeFromSuperview];
self.activityIndicator = nil;
if (self.animateOutCompletionBlock)
self.animateOutCompletionBlock();
self.animateOutCompletionBlock = nil;
}
@end
// Copyright 2016 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_BROWSER_UI_READING_LIST_NUMBER_BADGE_VIEW_H_
#define IOS_CHROME_BROWSER_UI_READING_LIST_NUMBER_BADGE_VIEW_H_
#import <UIKit/UIKit.h>
// Provides a view that displays a number in white text on a badge that starts
// round and stretches to a pill-shape to fit the number displayed. Displays
// only positive integers: negative/0 values will result in the badge being
// hidden.
@interface NumberBadgeView : UIView
// Set the number displayed by the badge. A value <=0 will cause the badge to be
// hidden.
- (void)setNumber:(NSInteger)number animated:(BOOL)animated;
// Set the color of the badge (the text is white).
- (void)setBackgroundColor:(UIColor*)backgroundColor animated:(BOOL)animated;
@end
#endif // IOS_CHROME_BROWSER_UI_READING_LIST_NUMBER_BADGE_VIEW_H_
// Copyright 2016 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/reading_list/number_badge_view.h"
#import <Foundation/Foundation.h>
#include "base/format_macros.h"
#import "ios/chrome/common/material_timing.h"
#import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoFontLoader.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
const CGFloat kAnimationDuration = ios::material::kDuration3;
const CGFloat labelMargin = 2.5f;
} // namespace
@interface NumberBadgeView ()
@property(nonatomic, readonly, weak) UILabel* label;
// This is a pill-shaped (rounded corners) view used in the background of the
// badge.
@property(nonatomic, readonly, weak) UIView* backgroundCircleView;
@property(nonatomic, assign) NSInteger displayNumber;
@end
@implementation NumberBadgeView
#pragma mark - properties
@synthesize label = _label;
@synthesize backgroundCircleView = _backgroundCircleView;
@synthesize displayNumber = _displayNumber;
#pragma mark - lifecycle
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
UIView* backgroundCircleView = [[UIView alloc] initWithFrame:CGRectZero];
_backgroundCircleView = backgroundCircleView;
[backgroundCircleView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:backgroundCircleView];
UILabel* label = [[UILabel alloc] initWithFrame:CGRectZero];
_label = label;
[label setFont:[[MDFRobotoFontLoader sharedInstance] boldFontOfSize:10]];
[label setTranslatesAutoresizingMaskIntoConstraints:NO];
[label setTextColor:[UIColor whiteColor]];
[self addSubview:label];
[NSLayoutConstraint activateConstraints:@[
// Position bubble.
[backgroundCircleView.trailingAnchor
constraintEqualToAnchor:self.trailingAnchor],
[backgroundCircleView.centerYAnchor
constraintEqualToAnchor:self.centerYAnchor],
// Position label on bubble.
[label.centerXAnchor
constraintEqualToAnchor:backgroundCircleView.centerXAnchor],
[label.centerYAnchor
constraintEqualToAnchor:backgroundCircleView.centerYAnchor],
// Make bubble fit label.
[backgroundCircleView.heightAnchor
constraintEqualToAnchor:label.heightAnchor
constant:labelMargin * 2],
[backgroundCircleView.widthAnchor
constraintGreaterThanOrEqualToAnchor:backgroundCircleView
.heightAnchor],
[backgroundCircleView.widthAnchor
constraintGreaterThanOrEqualToAnchor:label.widthAnchor
constant:labelMargin * 2]
]];
// Start hidden.
self.alpha = 0.0;
}
return self;
}
#pragma mark - UIView
- (void)layoutSubviews {
[super layoutSubviews];
self.backgroundCircleView.layer.cornerRadius =
self.backgroundCircleView.bounds.size.height / 2.0f;
}
#pragma mark - public
- (void)setNumber:(NSInteger)number animated:(BOOL)animated {
// If the previous number and current number match, do nothing.
if (self.displayNumber != number) {
self.displayNumber = number;
if (animated) {
[UIView animateWithDuration:kAnimationDuration
animations:^{
if (number > 0) {
self.alpha = 1.0;
// Only setting when > 0 as this makes the animation
// look better than switching to 0 then fading out.
self.label.text =
[NSString stringWithFormat:@"%" PRIdNS, number];
} else {
self.alpha = 0.0;
}
[self setNeedsLayout];
[self layoutIfNeeded];
}];
} else {
self.label.text = [NSString stringWithFormat:@"%" PRIdNS, number];
}
}
}
- (void)setBackgroundColor:(UIColor*)backgroundColor animated:(BOOL)animated {
if (animated) {
[UIView animateWithDuration:kAnimationDuration
animations:^{
[self.backgroundCircleView
setBackgroundColor:backgroundColor];
}];
} else {
[self.backgroundCircleView setBackgroundColor:backgroundColor];
}
}
@end
// Copyright 2016 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_BROWSER_UI_READING_LIST_OFFLINE_PAGE_NATIVE_CONTENT_H_
#define IOS_CHROME_BROWSER_UI_READING_LIST_OFFLINE_PAGE_NATIVE_CONTENT_H_
#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
class GURL;
namespace web {
class BrowserState;
class WebState;
}
@protocol UrlLoader;
// A |StaticHtmlNativeContent| that displays offline pages located in the
// application data folder that have been distilled by DOMdistiller.
@interface OfflinePageNativeContent : StaticHtmlNativeContent
// Initialization method.
// |loader| is the loader to use to follow navigation. Cannot be nil.
// |browserState| is the user browser state and must not be null.
// |URL| is the url of the page. The format of the URL must be
// chrome://offline/distillation_id/page.html
// If |URL| contain a virtual URL in its query params, this will be returned by
// the |OfflinePageNativeContent virtualURL| method.
- (instancetype)initWithLoader:(id<UrlLoader>)loader
browserState:(web::BrowserState*)browserState
webState:(web::WebState*)webState
URL:(const GURL&)URL NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithLoader:(id<UrlLoader>)loader
staticHTMLViewController:(StaticHtmlViewController*)HTMLViewController
URL:(const GURL&)URL NS_UNAVAILABLE;
@end
#endif // IOS_CHROME_BROWSER_UI_READING_LIST_OFFLINE_PAGE_NATIVE_CONTENT_H_
// Copyright 2016 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/reading_list/offline_page_native_content.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "components/reading_list/core/reading_list_switches.h"
#include "components/reading_list/ios/reading_list_model.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/reading_list/offline_url_utils.h"
#include "ios/chrome/browser/reading_list/reading_list_entry_loading_util.h"
#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
#import "ios/chrome/browser/ui/static_content/static_html_view_controller.h"
#include "ios/web/public/browser_state.h"
#include "net/base/network_change_notifier.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#pragma mark -
#pragma mark Public
@implementation OfflinePageNativeContent {
// The virtual URL that will be displayed to the user.
GURL _virtualURL;
// The Reading list model needed to reload the entry.
ReadingListModel* _model;
// The WebState of the current tab.
web::WebState* _webState;
}
- (instancetype)initWithLoader:(id<UrlLoader>)loader
browserState:(web::BrowserState*)browserState
webState:(web::WebState*)webState
URL:(const GURL&)URL {
DCHECK(loader);
DCHECK(browserState);
DCHECK(URL.is_valid());
if (reading_list::switches::IsReadingListEnabled()) {
_model = ReadingListModelFactory::GetForBrowserState(
ios::ChromeBrowserState::FromBrowserState(browserState));
}
_webState = webState;
GURL resourcesRoot;
GURL fileURL = reading_list::FileURLForDistilledURL(
URL, browserState->GetStatePath(), &resourcesRoot);
StaticHtmlViewController* HTMLViewController =
[[StaticHtmlViewController alloc] initWithFileURL:fileURL
allowingReadAccessToURL:resourcesRoot
browserState:browserState];
_virtualURL = reading_list::VirtualURLForDistilledURL(URL);
return [super initWithLoader:loader
staticHTMLViewController:HTMLViewController
URL:URL];
}
- (GURL)virtualURL {
return _virtualURL;
}
- (void)reload {
if (!_model || net::NetworkChangeNotifier::IsOffline()) {
[super reload];
return;
}
const ReadingListEntry* entry = _model->GetEntryByURL([self virtualURL]);
if (entry) {
reading_list::LoadReadingListEntry(*entry, _model, _webState);
} else {
[super reload];
}
}
@end
// Copyright 2016 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/reading_list/offline_page_native_content.h"
#include <memory>
#import "base/mac/scoped_nsobject.h"
#include "base/strings/sys_string_conversions.h"
#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
#include "ios/chrome/browser/reading_list/offline_url_utils.h"
#import "ios/chrome/browser/ui/static_content/static_html_view_controller.h"
#import "ios/chrome/browser/ui/url_loader.h"
#import "ios/web/public/test/web_test_with_web_state.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gtest_mac.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#include "third_party/ocmock/gtest_support.h"
class OfflinePageNativeContentTest : public web::WebTestWithWebState {
protected:
void SetUp() override {
web::WebTestWithWebState::SetUp();
TestChromeBrowserState::Builder test_cbs_builder;
chrome_browser_state_ = test_cbs_builder.Build();
}
std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
};
// Checks the OfflinePageNativeContent is correctly initialized.
TEST_F(OfflinePageNativeContentTest, BasicOfflinePageTest) {
GURL foo_url("http://foo.bar");
GURL url = reading_list::DistilledURLForPath(
base::FilePath("offline_id/page.html"), foo_url);
id<UrlLoader> loader = [OCMockObject mockForProtocol:@protocol(UrlLoader)];
base::scoped_nsobject<OfflinePageNativeContent> content(
[[OfflinePageNativeContent alloc]
initWithLoader:loader
browserState:chrome_browser_state_.get()
webState:web_state()
URL:url]);
ASSERT_EQ(url, [content url]);
ASSERT_EQ(foo_url, [content virtualURL]);
ASSERT_OCMOCK_VERIFY((OCMockObject*)loader);
}
TEST_F(OfflinePageNativeContentTest, BasicOfflinePageTestWithoutVirtualURL) {
GURL url = reading_list::DistilledURLForPath(
base::FilePath("offline_id/page.html"), GURL());
id<UrlLoader> loader = [OCMockObject mockForProtocol:@protocol(UrlLoader)];
base::scoped_nsobject<OfflinePageNativeContent> content(
[[OfflinePageNativeContent alloc]
initWithLoader:loader
browserState:chrome_browser_state_.get()
webState:web_state()
URL:url]);
ASSERT_EQ(url, [content url]);
ASSERT_EQ(url, [content virtualURL]);
ASSERT_OCMOCK_VERIFY((OCMockObject*)loader);
}
// Copyright 2016 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_BROWSER_UI_READING_LIST_READING_LIST_COLLECTION_VIEW_ITEM_H_
#define IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_COLLECTION_VIEW_ITEM_H_
#import "components/reading_list/ios/reading_list_entry.h"
#import "ios/chrome/browser/favicon/favicon_attributes_provider.h"
#import "ios/chrome/browser/ui/collection_view/cells/collection_view_text_item.h"
#import "ios/third_party/material_components_ios/src/components/CollectionCells/src/MaterialCollectionCells.h"
class GURL;
// Collection view item for representing a ReadingListEntry.
@interface ReadingListCollectionViewItem : CollectionViewItem
// The main text to display.
@property(nonatomic, copy) NSString* text;
// The secondary text to display.
@property(nonatomic, copy) NSString* detailText;
// The URL of the Reading List entry.
@property(nonatomic, readonly) const GURL& url;
// Designated initializer. The |provider| will be used for loading favicon
// attributes. The |delegate| is used to inform about changes to this item. The
// |url| is displayed as a subtitle. The |state| is used to display visual
// indicator of the distillation status.
- (instancetype)initWithType:(NSInteger)type
attributesProvider:(FaviconAttributesProvider*)provider
url:(const GURL&)url
distillationState:(ReadingListEntry::DistillationState)state
NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithType:(NSInteger)type NS_UNAVAILABLE;
@end
@class FaviconViewNew;
// Cell for ReadingListCollectionViewItem.
@interface ReadingListCell : MDCCollectionViewCell
// Title label.
@property(nonatomic, readonly, strong) UILabel* textLabel;
// Detail label.
@property(nonatomic, readonly, strong) UILabel* detailTextLabel;
// View for displaying the favicon for the reading list entry.
@property(nonatomic, readonly, strong) FaviconViewNew* faviconView;
// Status of the offline version. Updates the visual indicator when updated.
@property(nonatomic, assign)
ReadingListEntry::DistillationState distillationState;
@end
#endif // IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_COLLECTION_VIEW_ITEM_H_
// Copyright 2016 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/reading_list/reading_list_collection_view_item.h"
#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
#import "ios/chrome/browser/ui/favicon_view.h"
#import "ios/chrome/browser/ui/uikit_ui_util.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"
#import "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
NSString* kSuccessImageString = @"distillation_success";
NSString* kFailureImageString = @"distillation_fail";
// Distillation indicator constants.
const CGFloat kDistillationIndicatorSize = 18;
} // namespace
#pragma mark - ReadingListCell Private interface
@protocol ReadingListCellDelegate<NSObject>
- (void)readingListCellWillPrepareForReload:(ReadingListCell*)cell;
@end
@interface ReadingListCell ()
@property(nonatomic, weak) id<ReadingListCellDelegate> delegate;
@end
#pragma mark - ReadingListCollectionViewItem
@interface ReadingListCollectionViewItem ()<ReadingListCellDelegate> {
GURL _url;
ReadingListEntry::DistillationState _distillationState;
}
// Attributes provider used to retrieve favicons.
@property(nonatomic, strong)
FaviconAttributesProvider* faviconAttributesProvider;
// Attributes for favicon. Fetched in init, then retained for future updates.
@property(nonatomic, strong) FaviconAttributes* attributes;
// The cell that is displaying this item, if any. Used to reload favicon when
// the cell is on screen. Backed by WeakNSObject.
@property(nonatomic, weak) ReadingListCell* displayedCell;
@end
@implementation ReadingListCollectionViewItem
@synthesize faviconAttributesProvider = _faviconAttributesProvider;
@synthesize attributes = _attributes;
@synthesize text = _text;
@synthesize detailText = _detailText;
@synthesize url = _url;
@synthesize displayedCell = _displayedCell;
- (instancetype)initWithType:(NSInteger)type
attributesProvider:(FaviconAttributesProvider*)provider
url:(const GURL&)url
distillationState:(ReadingListEntry::DistillationState)state {
self = [super initWithType:type];
if (!self)
return nil;
self.cellClass = [ReadingListCell class];
_faviconAttributesProvider = provider;
_url = url;
_distillationState = state;
// |self| owns |provider|, |provider| owns the block, so a week self reference
// is necessary.
__weak ReadingListCollectionViewItem* weakSelf = self;
[provider
fetchFaviconAttributesForURL:url
completion:^(FaviconAttributes* _Nonnull attributes) {
ReadingListCollectionViewItem* strongSelf = weakSelf;
if (!strongSelf) {
return;
}
strongSelf.attributes = attributes;
[strongSelf.displayedCell.faviconView
configureWithAttributes:attributes];
}];
return self;
}
#pragma mark - CollectionViewTextItem
- (void)configureCell:(ReadingListCell*)cell {
[super configureCell:cell];
if (self.attributes) {
[cell.faviconView configureWithAttributes:self.attributes];
}
cell.textLabel.text = self.text;
cell.detailTextLabel.text = self.detailText;
self.displayedCell = cell;
cell.delegate = self;
cell.distillationState = _distillationState;
}
#pragma mark - ReadingListCellDelegate
- (void)readingListCellWillPrepareForReload:(ReadingListCell*)cell {
self.displayedCell = nil;
}
#pragma mark - NSObject
- (NSString*)description {
return [NSString stringWithFormat:@"Reading List item \"%@\" for url %@",
self.text, self.detailText];
}
@end
#pragma mark - ReadingListCell
@implementation ReadingListCell {
UIImageView* _downloadIndicator;
}
@synthesize faviconView = _faviconView;
@synthesize textLabel = _textLabel;
@synthesize detailTextLabel = _detailTextLabel;
@synthesize distillationState = _distillationState;
@synthesize delegate = _delegate;
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
MDFRobotoFontLoader* fontLoader = [MDFRobotoFontLoader sharedInstance];
CGFloat faviconSize = kFaviconPreferredSize;
_textLabel = [[UILabel alloc] init];
_textLabel.font = [fontLoader mediumFontOfSize:16];
_textLabel.textColor = [[MDCPalette greyPalette] tint900];
_detailTextLabel = [[UILabel alloc] init];
_detailTextLabel.font = [fontLoader mediumFontOfSize:14];
_detailTextLabel.textColor = [[MDCPalette greyPalette] tint500];
_faviconView = [[FaviconViewNew alloc] init];
CGFloat fontSize = floorf(faviconSize / 2);
[_faviconView setFont:[fontLoader regularFontOfSize:fontSize]];
_downloadIndicator = [[UIImageView alloc] init];
[_downloadIndicator setTranslatesAutoresizingMaskIntoConstraints:NO];
[_faviconView addSubview:_downloadIndicator];
UIStackView* labelsStack = [[UIStackView alloc]
initWithArrangedSubviews:@[ _textLabel, _detailTextLabel ]];
labelsStack.axis = UILayoutConstraintAxisVertical;
UIStackView* stackView = [[UIStackView alloc]
initWithArrangedSubviews:@[ _faviconView, labelsStack ]];
[self.contentView addSubview:stackView];
stackView.layoutMarginsRelativeArrangement = YES;
stackView.layoutMargins = UIEdgeInsetsMake(16, 16, 16, 16);
stackView.alignment = UIStackViewAlignmentCenter;
stackView.spacing = 16;
stackView.translatesAutoresizingMaskIntoConstraints = NO;
AddSameSizeConstraint(self.contentView, stackView);
[NSLayoutConstraint activateConstraints:@[
// Favicons are always the same size.
[_faviconView.widthAnchor constraintEqualToConstant:faviconSize],
[_faviconView.heightAnchor constraintEqualToConstant:faviconSize],
// Place the download indicator in the bottom right corner of the favicon.
[[_downloadIndicator centerXAnchor]
constraintEqualToAnchor:_faviconView.trailingAnchor],
[[_downloadIndicator centerYAnchor]
constraintEqualToAnchor:_faviconView.bottomAnchor],
[[_downloadIndicator widthAnchor]
constraintEqualToConstant:kDistillationIndicatorSize],
[[_downloadIndicator heightAnchor]
constraintEqualToConstant:kDistillationIndicatorSize],
]];
self.editingSelectorColor = [[MDCPalette cr_bluePalette] tint500];
}
return self;
}
- (void)setDistillationState:
(ReadingListEntry::DistillationState)distillationState {
if (_distillationState == distillationState)
return;
_distillationState = distillationState;
switch (distillationState) {
case ReadingListEntry::ERROR:
[_downloadIndicator setImage:[UIImage imageNamed:kFailureImageString]];
break;
case ReadingListEntry::PROCESSED:
[_downloadIndicator setImage:[UIImage imageNamed:kSuccessImageString]];
break;
// Same behavior for all pre-download states.
case ReadingListEntry::WAITING:
case ReadingListEntry::WILL_RETRY:
case ReadingListEntry::PROCESSING:
[_downloadIndicator setImage:nil];
break;
}
}
#pragma mark UICollectionViewCell
- (void)prepareForReuse {
[self.delegate readingListCellWillPrepareForReload:self];
self.delegate = nil;
self.textLabel.text = nil;
self.detailTextLabel.text = nil;
self.distillationState = ReadingListEntry::WAITING;
[super prepareForReuse];
}
@end
This diff is collapsed.
// Copyright 2016 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_BROWSER_UI_READING_LIST_READING_LIST_MENU_NOTIFICATION_DELEGATE_H_
#define IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_MENU_NOTIFICATION_DELEGATE_H_
#import <Foundation/Foundation.h>
// Protocol to implement in order to be delegate for reading list changes
// impacting the menu.
@protocol ReadingListMenuNotificationDelegate<NSObject>
// Called when the reading list menu unread count has changed.
- (void)unreadCountChanged:(NSInteger)unreadCount;
// Called when the reading list menu seen state has changed.
- (void)unseenStateChanged:(BOOL)unseenItemsExist;
@end
#endif // IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_MENU_NOTIFICATION_DELEGATE_H_
// Copyright 2016 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_BROWSER_UI_READING_LIST_READING_LIST_MENU_NOTIFIER_H_
#define IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_MENU_NOTIFIER_H_
#import <Foundation/Foundation.h>
class ReadingListModel;
@protocol ReadingListMenuNotificationDelegate;
// Notifies its delegate of changes in the reading list that have an impact on
// the menu. Can also be queried for current values of the model.
@interface ReadingListMenuNotifier : NSObject
// Delegate for handling of changes in the reading list model.
@property(nonatomic, weak) id<ReadingListMenuNotificationDelegate> delegate;
- (instancetype)initWithReadingList:(ReadingListModel*)readingListModel;
// The number of unread items in the reading list.
- (NSInteger)readingListUnreadCount;
// Whether the reading list has unseen items.
- (BOOL)readingListUnseenItemsExist;
@end
#endif // IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_MENU_NOTIFIER_H_
// Copyright 2016 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/reading_list/reading_list_menu_notifier.h"
#include <memory>
#include "components/reading_list/ios/reading_list_model.h"
#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notification_delegate.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
class ReadingListObserverBridge;
@interface ReadingListMenuNotifier () {
// Observer for reading list changes.
std::unique_ptr<ReadingListObserverBridge> _readingListObserverBridge;
// Backing object for property of the same name.
__weak id<ReadingListMenuNotificationDelegate> _delegate;
// Keep a reference to detach before deallocing.
ReadingListModel* _readingListModel; // weak
}
// Detach the observer on the reading list.
- (void)detachReadingListModel;
// Handle callbacks from the reading list model observer.
- (void)readingListModelCompletedBatchUpdates:(const ReadingListModel*)model;
@end
// TODO(crbug.com/590725): use the one-and-only protocol-based implementation of
// ReadingListModelObserver
class ReadingListObserverBridge : public ReadingListModelObserver {
public:
explicit ReadingListObserverBridge(ReadingListMenuNotifier* owner)
: owner_(owner) {}
~ReadingListObserverBridge() override {}
void ReadingListModelLoaded(const ReadingListModel* model) override {}
void ReadingListModelBeganBatchUpdates(
const ReadingListModel* model) override {}
void ReadingListModelCompletedBatchUpdates(
const ReadingListModel* model) override {
[owner_ readingListModelCompletedBatchUpdates:model];
}
void ReadingListModelBeingDeleted(const ReadingListModel* model) override{};
void ReadingListDidApplyChanges(ReadingListModel* model) override {
[owner_ readingListModelCompletedBatchUpdates:model];
}
private:
ReadingListMenuNotifier* owner_; // weak, owns us
};
@implementation ReadingListMenuNotifier
@synthesize delegate = _delegate;
- (instancetype)initWithReadingList:(ReadingListModel*)readingListModel {
if (self = [super init]) {
_readingListObserverBridge.reset(new ReadingListObserverBridge(self));
_readingListModel = readingListModel;
_readingListModel->AddObserver(_readingListObserverBridge.get());
}
return self;
}
- (void)dealloc {
[self detachReadingListModel];
}
- (void)detachReadingListModel {
_readingListModel->RemoveObserver(_readingListObserverBridge.get());
_readingListObserverBridge.reset();
}
- (void)readingListModelCompletedBatchUpdates:(const ReadingListModel*)model {
[_delegate unreadCountChanged:model->unread_size()];
[_delegate unseenStateChanged:model->HasUnseenEntries()];
}
- (NSInteger)readingListUnreadCount {
return _readingListModel->unread_size();
}
- (BOOL)readingListUnseenItemsExist {
return _readingListModel->HasUnseenEntries();
}
@end
// Copyright 2016 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_BROWSER_UI_READING_LIST_READING_LIST_SIDE_SWIPE_PROVIDER_H_
#define IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_SIDE_SWIPE_PROVIDER_H_
#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
class ReadingListModel;
@interface ReadingListSideSwipeProvider : NSObject<SideSwipeContentProvider>
- (instancetype)initWithReadingList:(ReadingListModel*)readingListModel;
@end
#endif // IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_SIDE_SWIPE_PROVIDER_H_
// Copyright 2016 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/reading_list/reading_list_side_swipe_provider.h"
#include "base/logging.h"
#include "components/reading_list/ios/reading_list_entry.h"
#include "components/reading_list/ios/reading_list_model.h"
#include "ios/chrome/browser/reading_list/reading_list_entry_loading_util.h"
#include "ios/web/public/web_state/web_state.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
class ReadingListObserverBridge;
@interface ReadingListSideSwipeProvider () {
// Keep a reference to detach before deallocing.
ReadingListModel* _readingListModel; // weak
}
@end
@implementation ReadingListSideSwipeProvider
- (instancetype)initWithReadingList:(ReadingListModel*)readingListModel {
if (self = [super init]) {
_readingListModel = readingListModel;
}
return self;
}
- (BOOL)canGoBack {
return NO;
}
- (void)goBack:(web::WebState*)webState {
NOTREACHED();
}
- (BOOL)canGoForward {
return _readingListModel->unread_size() > 0;
}
- (UIImage*)paneIcon {
return [UIImage imageNamed:@"reading_list_side_swipe"];
}
- (BOOL)rotateForwardIcon {
return NO;
}
- (void)goForward:(web::WebState*)webState {
if (!webState || _readingListModel->unread_size() == 0) {
return;
}
int64_t updatetime = 0;
const ReadingListEntry* first_entry = nullptr;
for (const auto& url : _readingListModel->Keys()) {
const ReadingListEntry* entry = _readingListModel->GetEntryByURL(url);
if (!entry->IsRead() && entry->UpdateTime() > updatetime) {
updatetime = entry->UpdateTime();
first_entry = entry;
}
}
DCHECK_GT(updatetime, 0);
DCHECK_NE(first_entry, nullptr);
reading_list::LoadReadingListEntry(*first_entry, _readingListModel, webState);
}
@end
// Copyright 2016 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_BROWSER_UI_READING_LIST_READING_LIST_TOOLBAR_H_
#define IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_TOOLBAR_H_
#import <UIKit/UIKit.h>
@class ActionSheetCoordinator;
typedef NS_ENUM(NSInteger, ReadingListToolbarState) {
NoneSelected,
OnlyReadSelected,
OnlyUnreadSelected,
MixedItemsSelected
};
@protocol ReadingListToolbarDelegate<NSObject>
// Callback for the toolbar mark button.
- (void)markPressed;
// Callback for the toolbar delete button.
- (void)deletePressed;
// Enters editing mode. Updates the toolbar.
- (void)enterEditingModePressed;
// Exits editing mode. Updates the toolbar.
- (void)exitEditingModePressed;
@end
// View at the bottom of the reading list panel that presents options to edit
// the entries. When editing, the interface changes, allowing the user to delete
// them and mark them read/unread.
@interface ReadingListToolbar : UIView
// The toolbar state. The text of the buttons change to reflect the state.
@property(nonatomic, assign) ReadingListToolbarState state;
// Delegate handling the button callbacks.
@property(nonatomic, weak) id<ReadingListToolbarDelegate> delegate;
// Informs the toolbar whether there are read items. The "Delete All Read"
// button will be enabled accordingly.
- (void)setHasReadItem:(BOOL)hasRead;
// Sets the editing mode for the toolbar, showing/hiding buttons accordingly.
- (void)setEditing:(BOOL)editing;
// Returns an empty ActionSheetCoordiantor anchored to the mark button with no
// message and no title.
- (ActionSheetCoordinator*)actionSheetForMarkWithBaseViewController:
(UIViewController*)viewController;
@end
#endif // IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_TOOLBAR_H_
// Copyright 2016 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/reading_list/reading_list_toolbar.h"
#include "base/logging.h"
#import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.h"
#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
#import "ios/chrome/browser/ui/uikit_ui_util.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h"
#include "ui/base/l10n/l10n_util_mac.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Shadow opacity.
const CGFloat kShadowOpacity = 0.2f;
// Horizontal margin for the content.
const CGFloat kHorizontalMargin = 8.0f;
} // namespace
@interface ReadingListToolbar ()
// Container for the edit button, preventing it to have the same width as the
// stack view.
@property(nonatomic, strong) UIView* editButtonContainer;
// Button that displays "Delete".
@property(nonatomic, strong) UIButton* deleteButton;
// Button that displays "Delete All Read".
@property(nonatomic, strong) UIButton* deleteAllButton;
// Button that displays "Cancel".
@property(nonatomic, strong) UIButton* cancelButton;
// Button that displays the mark options.
@property(nonatomic, strong) UIButton* markButton;
// Stack view for arranging the buttons.
@property(nonatomic, strong) UIStackView* stackView;
// Creates a button with a |title| and a style according to |destructive|.
- (UIButton*)buttonWithText:(NSString*)title destructive:(BOOL)isDestructive;
// Set the mark button label to |text|.
- (void)setMarkButtonText:(NSString*)text;
// Updates the button labels to match an empty selection.
- (void)updateButtonsForEmptySelection;
// Updates the button labels to match a selection containing only read items.
- (void)updateButtonsForOnlyReadSelection;
// Updates the button labels to match a selection containing only unread items.
- (void)updateButtonsForOnlyUnreadSelection;
// Updates the button labels to match a selection containing unread and read
// items.
- (void)updateButtonsForOnlyMixedSelection;
// Action for the Edit button.
- (void)enterEdit;
// Action for the Cancel button.
- (void)exitEdit;
// Action for the Mark button.
- (void)markAction;
// Action for the Delete button.
- (void)deleteAction;
@end
@implementation ReadingListToolbar
@synthesize editButtonContainer = _editButtonContainer;
@synthesize deleteButton = _deleteButton;
@synthesize deleteAllButton = _deleteAllButton;
@synthesize cancelButton = _cancelButton;
@synthesize stackView = _stackView;
@synthesize markButton = _markButton;
@synthesize state = _state;
@synthesize delegate = _delegate;
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
UIButton* editButton = [self
buttonWithText:l10n_util::GetNSString(IDS_IOS_READING_LIST_EDIT_BUTTON)
destructive:NO];
_deleteButton = [self buttonWithText:l10n_util::GetNSString(
IDS_IOS_READING_LIST_DELETE_BUTTON)
destructive:YES];
_deleteAllButton =
[self buttonWithText:l10n_util::GetNSString(
IDS_IOS_READING_LIST_DELETE_ALL_READ_BUTTON)
destructive:YES];
_markButton = [self buttonWithText:l10n_util::GetNSString(
IDS_IOS_READING_LIST_MARK_ALL_BUTTON)
destructive:NO];
_cancelButton = [self buttonWithText:l10n_util::GetNSString(
IDS_IOS_READING_LIST_CANCEL_BUTTON)
destructive:NO];
[editButton addTarget:self
action:@selector(enterEdit)
forControlEvents:UIControlEventTouchUpInside];
[_deleteButton addTarget:self
action:@selector(deleteAction)
forControlEvents:UIControlEventTouchUpInside];
[_deleteAllButton addTarget:self
action:@selector(deleteAction)
forControlEvents:UIControlEventTouchUpInside];
[_markButton addTarget:self
action:@selector(markAction)
forControlEvents:UIControlEventTouchUpInside];
[_cancelButton addTarget:self
action:@selector(exitEdit)
forControlEvents:UIControlEventTouchUpInside];
_editButtonContainer = [[UIView alloc] initWithFrame:CGRectZero];
[_editButtonContainer addSubview:editButton];
editButton.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary* views = @{ @"button" : editButton };
NSArray* constraints = @[ @"V:|[button]|", @"H:[button]|" ];
ApplyVisualConstraints(constraints, views);
_stackView = [[UIStackView alloc] initWithArrangedSubviews:@[
_editButtonContainer, _deleteButton, _deleteAllButton, _markButton,
_cancelButton
]];
_stackView.axis = UILayoutConstraintAxisHorizontal;
_stackView.alignment = UIStackViewAlignmentFill;
_stackView.distribution = UIStackViewDistributionEqualCentering;
[self addSubview:_stackView];
_stackView.translatesAutoresizingMaskIntoConstraints = NO;
AddSameSizeConstraint(_stackView, self);
_stackView.layoutMargins =
UIEdgeInsetsMake(0, kHorizontalMargin, 0, kHorizontalMargin);
_stackView.layoutMarginsRelativeArrangement = YES;
self.backgroundColor = [UIColor whiteColor];
[[self layer] setShadowOpacity:kShadowOpacity];
[self setEditing:NO];
}
return self;
}
#pragma mark Public Methods
- (void)setEditing:(BOOL)editing {
self.editButtonContainer.hidden = editing;
self.deleteButton.hidden = YES;
self.deleteAllButton.hidden = !editing;
self.cancelButton.hidden = !editing;
self.markButton.hidden = !editing;
}
- (void)setState:(ReadingListToolbarState)state {
switch (state) {
case NoneSelected:
[self updateButtonsForEmptySelection];
break;
case OnlyReadSelected:
[self updateButtonsForOnlyReadSelection];
break;
case OnlyUnreadSelected:
[self updateButtonsForOnlyUnreadSelection];
break;
case MixedItemsSelected:
[self updateButtonsForOnlyMixedSelection];
break;
}
_state = state;
}
- (void)setHasReadItem:(BOOL)hasRead {
self.deleteAllButton.enabled = hasRead;
}
- (ActionSheetCoordinator*)actionSheetForMarkWithBaseViewController:
(UIViewController*)viewController {
return [[ActionSheetCoordinator alloc]
initWithBaseViewController:viewController
title:nil
message:nil
rect:self.markButton.bounds
view:self.markButton];
}
#pragma mark Private Methods
- (void)enterEdit {
[_delegate enterEditingModePressed];
}
- (void)exitEdit {
[_delegate exitEditingModePressed];
}
- (void)markAction {
[_delegate markPressed];
}
- (void)deleteAction {
[_delegate deletePressed];
}
- (void)updateButtonsForEmptySelection {
self.deleteAllButton.hidden = NO;
self.deleteButton.hidden = YES;
[self setMarkButtonText:l10n_util::GetNSStringWithFixup(
IDS_IOS_READING_LIST_MARK_ALL_BUTTON)];
}
- (void)updateButtonsForOnlyReadSelection {
self.deleteAllButton.hidden = YES;
self.deleteButton.hidden = NO;
[self setMarkButtonText:l10n_util::GetNSStringWithFixup(
IDS_IOS_READING_LIST_MARK_UNREAD_BUTTON)];
}
- (void)updateButtonsForOnlyUnreadSelection {
self.deleteAllButton.hidden = YES;
self.deleteButton.hidden = NO;
[self setMarkButtonText:l10n_util::GetNSStringWithFixup(
IDS_IOS_READING_LIST_MARK_READ_BUTTON)];
}
- (void)updateButtonsForOnlyMixedSelection {
self.deleteAllButton.hidden = YES;
self.deleteButton.hidden = NO;
[self setMarkButtonText:l10n_util::GetNSStringWithFixup(
IDS_IOS_READING_LIST_MARK_BUTTON)];
}
- (UIButton*)buttonWithText:(NSString*)title destructive:(BOOL)isDestructive {
UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom];
button.contentEdgeInsets = UIEdgeInsetsMake(0, 8, 0, 8);
[button setTitle:title forState:UIControlStateNormal];
button.backgroundColor = [UIColor whiteColor];
UIColor* textColor = isDestructive ? [[MDCPalette cr_redPalette] tint500]
: [[MDCPalette cr_bluePalette] tint500];
[button setTitleColor:textColor forState:UIControlStateNormal];
[button setTitleColor:[UIColor lightGrayColor]
forState:UIControlStateDisabled];
[[button titleLabel]
setFont:[[MDCTypography fontLoader] regularFontOfSize:14]];
return button;
}
- (void)setMarkButtonText:(NSString*)text {
[self.markButton setTitle:text forState:UIControlStateNormal];
}
@end
// Copyright 2016 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_BROWSER_UI_READING_LIST_READING_LIST_VIEW_CONTROLLER_H_
#define IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_VIEW_CONTROLLER_H_
#import "ios/chrome/browser/ui/collection_view/collection_view_controller.h"
#include <string>
#import "ios/chrome/browser/ui/reading_list/reading_list_toolbar.h"
namespace favicon {
class LargeIconService;
}
class ReadingListDownloadService;
class ReadingListModel;
@class TabModel;
@interface ReadingListViewController
: CollectionViewController<ReadingListToolbarDelegate>
@property(nonatomic, readonly) ReadingListModel* readingListModel;
@property(weak, nonatomic, readonly) TabModel* tabModel;
@property(nonatomic, readonly) favicon::LargeIconService* largeIconService;
@property(nonatomic, readonly)
ReadingListDownloadService* readingListDownloadService;
- (instancetype)initWithModel:(ReadingListModel*)model
tabModel:(TabModel*)tabModel
largeIconService:(favicon::LargeIconService*)largeIconService
readingListDownloadService:
(ReadingListDownloadService*)readingListDownloadService
NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithStyle:(CollectionViewControllerStyle)style
NS_UNAVAILABLE;
@end
@interface ReadingListViewController (Testing)
// Dismisses this view controller.
- (void)dismiss;
@end
#endif // IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_VIEW_CONTROLLER_H_
// Copyright 2016 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_BROWSER_UI_READING_LIST_READING_LIST_VIEW_CONTROLLER_BUILDER_H_
#define IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_VIEW_CONTROLLER_BUILDER_H_
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/reading_list/reading_list_view_controller.h"
namespace ios {
class ChromeBrowserState;
}
@class TabModel;
@protocol ReadingListViewControllerDelegate;
// A builder class that constructs ReadingListViewControllers.
@interface ReadingListViewControllerBuilder : NSObject
+ (ReadingListViewController*)
readingListViewControllerInBrowserState:(ios::ChromeBrowserState*)browserState
tabModel:(TabModel*)tabModel;
@end
#endif // IOS_CHROME_BROWSER_UI_READING_LIST_READING_LIST_VIEW_CONTROLLER_BUILDER_H_
// Copyright 2016 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/reading_list/reading_list_view_controller_builder.h"
#include "components/prefs/pref_service.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h"
#include "ios/chrome/browser/pref_names.h"
#include "ios/chrome/browser/reading_list/reading_list_download_service.h"
#include "ios/chrome/browser/reading_list/reading_list_download_service_factory.h"
#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@implementation ReadingListViewControllerBuilder
+ (ReadingListViewController*)
readingListViewControllerInBrowserState:(ios::ChromeBrowserState*)browserState
tabModel:(TabModel*)tabModel {
ReadingListModel* model =
ReadingListModelFactory::GetInstance()->GetForBrowserState(browserState);
favicon::LargeIconService* service =
IOSChromeLargeIconServiceFactory::GetForBrowserState(browserState);
ReadingListDownloadService* rlservice =
ReadingListDownloadServiceFactory::GetInstance()->GetForBrowserState(
browserState);
ReadingListViewController* vc =
[[ReadingListViewController alloc] initWithModel:model
tabModel:tabModel
largeIconService:service
readingListDownloadService:rlservice];
return vc;
}
@end
// Copyright 2016 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/reading_list/reading_list_view_controller.h"
#include <unordered_set>
#include "base/mac/scoped_nsobject.h"
#include "base/memory/ptr_util.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/favicon/core/favicon_client.h"
#include "components/favicon/core/favicon_service.h"
#include "components/favicon/core/large_icon_service.h"
#include "components/reading_list/ios/reading_list_model.h"
#include "components/reading_list/ios/reading_list_model_impl.h"
#include "components/reading_list/ios/reading_list_model_storage.h"
#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
#include "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h"
#import "ios/chrome/browser/tabs/tab.h"
#import "ios/chrome/browser/tabs/tab_model.h"
#import "ios/chrome/browser/ui/url_loader.h"
#include "ios/web/public/navigation_item.h"
#include "ios/web/public/referrer.h"
#import "ios/web/public/test/web_test_with_web_state.h"
#include "ios/web/public/web_state/web_state_observer.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"
#pragma mark - MockFaviconService
// A mock FaviconService that emits an empty response.
class MockFaviconService : public favicon::FaviconService {
public:
MockFaviconService() : FaviconService(nullptr, nullptr) {}
~MockFaviconService() override {}
base::CancelableTaskTracker::TaskId GetLargestRawFaviconForPageURL(
const GURL& page_url,
const std::vector<int>& icon_types,
int minimum_size_in_pixels,
const favicon_base::FaviconRawBitmapCallback& callback,
base::CancelableTaskTracker* tracker) override {
favicon_base::FaviconRawBitmapResult mock_result;
return tracker->PostTask(base::ThreadTaskRunnerHandle::Get().get(),
FROM_HERE, base::Bind(callback, mock_result));
}
private:
DISALLOW_COPY_AND_ASSIGN(MockFaviconService);
};
#pragma mark - MockLargeIconService
// This class provides access to LargeIconService internals, using the current
// thread's task runner for testing.
class MockLargeIconService : public favicon::LargeIconService {
public:
explicit MockLargeIconService(MockFaviconService* mock_favicon_service)
: LargeIconService(mock_favicon_service,
base::ThreadTaskRunnerHandle::Get()) {}
~MockLargeIconService() override {}
private:
DISALLOW_COPY_AND_ASSIGN(MockLargeIconService);
};
#pragma mark - ReadingListViewControllerTest
class ReadingListViewControllerTest : public web::WebTestWithWebState {
public:
ReadingListViewControllerTest() {}
~ReadingListViewControllerTest() override {}
std::unique_ptr<ReadingListModelImpl> reading_list_model_;
std::unique_ptr<favicon::LargeIconService> large_icon_service_;
std::unique_ptr<MockFaviconService> mock_favicon_service_;
base::scoped_nsobject<ReadingListViewController>
reading_list_view_controller_;
// TODO(crbug.com/625617) When offline url can be opened, use a mock for the
// readinglistdownloadservice.
void SetUp() override {
web::WebTestWithWebState::SetUp();
mock_favicon_service_.reset(new MockFaviconService());
// OCMOCK_VALUE needs a lvalues.
web::WebState* mock_web_state = web_state();
id tab = [OCMockObject mockForClass:[Tab class]];
[[[tab stub] andReturnValue:OCMOCK_VALUE(mock_web_state)] webState];
id tabModel = [OCMockObject mockForClass:[TabModel class]];
[[[tabModel stub] andReturn:tab] currentTab];
reading_list_model_.reset(new ReadingListModelImpl(nullptr, nullptr));
large_icon_service_.reset(
new MockLargeIconService(mock_favicon_service_.get()));
reading_list_view_controller_.reset([[ReadingListViewController alloc]
initWithModel:reading_list_model_.get()
tabModel:tabModel
largeIconService:large_icon_service_.get()
readingListDownloadService:nil]);
}
DISALLOW_COPY_AND_ASSIGN(ReadingListViewControllerTest);
};
// Tests that reading list items are displayed.
TEST_F(ReadingListViewControllerTest, DisplaysItems) {
// Prefill some items.
reading_list_model_->AddEntry(GURL("https://chromium.org"), "news");
reading_list_model_->AddEntry(GURL("https://mail.chromium.org"), "mail");
reading_list_model_->AddEntry(GURL("https://foo.bar"), "Foo");
reading_list_model_->SetReadStatus(GURL("https://foo.bar"), true);
// Load view.
[reading_list_view_controller_ view];
// There are two sections: Read and Unread.
DCHECK(
[reading_list_view_controller_.get().collectionView numberOfSections] ==
2);
// There are two unread articles.
DCHECK([reading_list_view_controller_.get().collectionView
numberOfItemsInSection:0] == 2);
// There is one read article.
DCHECK([reading_list_view_controller_.get().collectionView
numberOfItemsInSection:1] == 1);
}
// Tests that the view controller is dismissed when Done button is pressed.
TEST_F(ReadingListViewControllerTest, GetsDismissed) {
// Load view.
[reading_list_view_controller_ view];
id partialMock =
[OCMockObject partialMockForObject:reading_list_view_controller_.get()];
[[partialMock expect] dismiss];
// Simulate tap on "Done" button.
UIBarButtonItem* done =
reading_list_view_controller_.get().navigationItem.rightBarButtonItem;
[done.target performSelector:done.action];
[partialMock verify];
}
// Tests that when an item is selected, the article is opened with UrlLoader and
// the view controller is dismissed.
TEST_F(ReadingListViewControllerTest, OpensItems) {
id partialMock =
[OCMockObject partialMockForObject:reading_list_view_controller_.get()];
GURL url("https://chromium.org");
reading_list_model_->AddEntry(url, "chromium");
[reading_list_view_controller_ view];
[[partialMock expect] dismiss];
// Simulate touch on first cell.
[reading_list_view_controller_.get().collectionView.delegate
collectionView:reading_list_view_controller_.get()
.collectionView
didSelectItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
web::NavigationItem* item =
web_state()->GetNavigationManager()->GetPendingItem();
EXPECT_EQ(item->GetURL().spec(), url.spec());
EXPECT_FALSE(item->GetReferrer().url.is_valid());
EXPECT_EQ(item->GetReferrer().policy,
web::ReferrerPolicy::ReferrerPolicyDefault);
EXPECT_TRUE(PageTransitionCoreTypeIs(
item->GetTransitionType(),
ui::PageTransition::PAGE_TRANSITION_AUTO_BOOKMARK));
[partialMock verify];
}
// 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.
#ifndef IOS_CHROME_BROWSER_UI_SAD_TAB_SAD_TAB_VIEW_H_
#define IOS_CHROME_BROWSER_UI_SAD_TAB_SAD_TAB_VIEW_H_
#import <UIKit/UIKit.h>
#include "base/ios/block_types.h"
// The view used to show "sad tab" content to the user when WKWebView's renderer
// process crashes.
@interface SadTabView : UIView
// Designated initializer. |reloadHandler| will be called when the reload
// button is tapped and must not be nil.
- (instancetype)initWithReloadHandler:(ProceduralBlock)reloadHandler
NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
@end
#endif // IOS_CHROME_BROWSER_UI_SAD_TAB_SAD_TAB_VIEW_H_
This diff is collapsed.
# Settings
-----
**Some of the files in this directory are only used in the new iOS Chrome
architecture:**
* `settings_coordinator.h` and `.mm`
-----
// 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.
#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_ABOUT_CHROME_COLLECTION_VIEW_CONTROLLER_H_
#define IOS_CHROME_BROWSER_UI_SETTINGS_ABOUT_CHROME_COLLECTION_VIEW_CONTROLLER_H_
#import "ios/chrome/browser/ui/settings/settings_root_collection_view_controller.h"
@interface AboutChromeCollectionViewController
: SettingsRootCollectionViewController
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithStyle:(CollectionViewControllerStyle)style
NS_UNAVAILABLE;
@end
#endif // IOS_CHROME_BROWSER_UI_SETTINGS_ABOUT_CHROME_COLLECTION_VIEW_CONTROLLER_H_
This diff is collapsed.
// 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.
#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_ACCOUNTS_COLLECTION_VIEW_CONTROLLER_H_
#define IOS_CHROME_BROWSER_UI_SETTINGS_ACCOUNTS_COLLECTION_VIEW_CONTROLLER_H_
#import "ios/chrome/browser/sync/sync_observer_bridge.h"
#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
#import "ios/chrome/browser/ui/settings/settings_root_collection_view_controller.h"
// The accessibility identifier of the view controller's view.
extern NSString* const kSettingsAccountsId;
// The accessibility identifier of the header of the accounts list.
extern NSString* const kSettingsHeaderId;
// The accessibility identifier of the signout cell.
extern NSString* const kSettingsAccountsSignoutCellId;
// The accessibility identifier of the sync account cell.
extern NSString* const kSettingsAccountsSyncCellId;
namespace ios {
class ChromeBrowserState;
} // namespace ios
// Collection View that handles the settings for accounts when the user is
// signed in
// to Chrome.
@interface AccountsCollectionViewController
: SettingsRootCollectionViewController<SettingsControllerProtocol>
// |browserState| must not be nil.
// If |closeSettingsOnAddAccount| is YES, then this account table view
// controller will close the setting screen when an account is added.
- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
closeSettingsOnAddAccount:(BOOL)closeSettingsOnAddAccount
NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithStyle:(CollectionViewControllerStyle)style
NS_UNAVAILABLE;
@end
#endif // IOS_CHROME_BROWSER_UI_SETTINGS_ACCOUNTS_COLLECTION_VIEW_CONTROLLER_H_
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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