blob: 7b34b391215c7e15d6fb1a273a981b5b97378bcb [file] [log] [blame]
// 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/mac/bind_objc_block.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"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@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(weak, 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];
}
- (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);
__weak 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::BindBlockArc(^(
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];
[view assignDistillerViewer:[self startDistillation]];
return view;
}
#pragma mark infobar.
- (void)showInfoBar:(const std::string&)html forURL:(const GURL&)url {
DCHECK(_webState);
__weak 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::BindBlockArc(^{
[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