blob: aeb8c84d0d534fea72c1d8b82240d5554a58cfa1 [file] [log] [blame]
// 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/browser/ui/fullscreen/fullscreen_mediator.h"
#include "base/check_op.h"
#include "base/memory/ptr_util.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_animator.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_content_adjustment_util.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller_observer.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_model.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_web_view_resizer.h"
#include "ios/chrome/browser/ui/util/ui_util.h"
#import "ios/web/public/web_state.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
FullscreenMediator::FullscreenMediator(FullscreenController* controller,
FullscreenModel* model)
: controller_(controller),
model_(model),
resizer_([[FullscreenWebViewResizer alloc] initWithModel:model]) {
DCHECK(controller_);
DCHECK(model_);
model_->AddObserver(this);
}
FullscreenMediator::~FullscreenMediator() {
// Disconnect() is expected to be called before deallocation.
DCHECK(!controller_);
DCHECK(!model_);
}
void FullscreenMediator::SetWebState(web::WebState* webState) {
resizer_.webState = webState;
}
void FullscreenMediator::SetIsBrowserTraitCollectionUpdating(bool updating) {
if (updating_browser_trait_collection_ == updating)
return;
updating_browser_trait_collection_ = updating;
if (updating_browser_trait_collection_) {
resizer_.compensateFrameChangeByOffset = NO;
scrolled_to_top_during_trait_collection_updates_ =
model_->is_scrolled_to_top();
} else {
resizer_.compensateFrameChangeByOffset = YES;
if (scrolled_to_top_during_trait_collection_updates_) {
// If the content was scrolled to the top when the trait collection began
// updating, changes in toolbar heights may cause the top of the page to
// become hidden. Ensure that the page remains scrolled to the top after
// the trait collection finishes updating.
web::WebState* web_state = resizer_.webState;
if (web_state)
MoveContentBelowHeader(web_state->GetWebViewProxy(), model_);
scrolled_to_top_during_trait_collection_updates_ = false;
}
}
}
void FullscreenMediator::EnterFullscreen() {
if (model_->enabled())
AnimateWithStyle(FullscreenAnimatorStyle::ENTER_FULLSCREEN);
}
void FullscreenMediator::ExitFullscreen() {
// Instruct the model to ignore the remainder of the current scroll when
// starting this animator. This prevents the toolbar from immediately being
// hidden if AnimateModelReset() is called while a scroll view is
// decelerating.
model_->IgnoreRemainderOfCurrentScroll();
AnimateWithStyle(FullscreenAnimatorStyle::EXIT_FULLSCREEN);
}
void FullscreenMediator::Disconnect() {
for (auto& observer : observers_) {
observer.FullscreenControllerWillShutDown(controller_);
}
resizer_.webState = nullptr;
resizer_ = nil;
[animator_ stopAnimation:YES];
animator_ = nil;
model_->RemoveObserver(this);
model_ = nullptr;
controller_ = nullptr;
}
void FullscreenMediator::FullscreenModelToolbarHeightsUpdated(
FullscreenModel* model) {
for (auto& observer : observers_) {
observer.FullscreenViewportInsetRangeChanged(controller_,
model_->min_toolbar_insets(),
model_->max_toolbar_insets());
}
// Changes in the toolbar heights modifies the visible viewport so the WebView
// needs to be resized as needed.
[resizer_ updateForCurrentState];
}
void FullscreenMediator::FullscreenModelProgressUpdated(
FullscreenModel* model) {
DCHECK_EQ(model_, model);
StopAnimating(true /* update_model */);
for (auto& observer : observers_) {
observer.FullscreenProgressUpdated(controller_, model_->progress());
}
[resizer_ updateForCurrentState];
}
void FullscreenMediator::FullscreenModelEnabledStateChanged(
FullscreenModel* model) {
DCHECK_EQ(model_, model);
StopAnimating(true /* update_model */);
for (auto& observer : observers_) {
observer.FullscreenEnabledStateChanged(controller_, model->enabled());
}
}
void FullscreenMediator::FullscreenModelScrollEventStarted(
FullscreenModel* model) {
DCHECK_EQ(model_, model);
StopAnimating(true /* update_model */);
// Show the toolbars if the user begins a scroll past the bottom edge of the
// screen and the toolbars have been fully collapsed.
if (model_->is_scrolled_to_bottom() &&
AreCGFloatsEqual(model_->progress(), 0.0) &&
model_->can_collapse_toolbar()) {
ExitFullscreen();
}
}
void FullscreenMediator::FullscreenModelScrollEventEnded(
FullscreenModel* model) {
DCHECK_EQ(model_, model);
AnimateWithStyle(model_->progress() >= 0.5
? FullscreenAnimatorStyle::EXIT_FULLSCREEN
: FullscreenAnimatorStyle::ENTER_FULLSCREEN);
}
void FullscreenMediator::FullscreenModelWasReset(FullscreenModel* model) {
// Stop any in-progress animations. Don't update the model because this
// callback occurs after the model's state is reset, and updating the model
// the with active animator's current value would overwrite the reset value.
StopAnimating(false /* update_model */);
// Update observers for the reset progress value and set the inset range in
// case this is a new WebState.
for (auto& observer : observers_) {
observer.FullscreenViewportInsetRangeChanged(
controller_, controller_->GetMinViewportInsets(),
controller_->GetMaxViewportInsets());
observer.FullscreenProgressUpdated(controller_, model_->progress());
}
[resizer_ updateForCurrentState];
}
void FullscreenMediator::AnimateWithStyle(FullscreenAnimatorStyle style) {
if (animator_ && animator_.style == style)
return;
StopAnimating(true);
DCHECK(!animator_);
// Early return if there is no progress change.
CGFloat start_progress = model_->progress();
CGFloat final_progress = GetFinalFullscreenProgressForAnimation(style);
if (AreCGFloatsEqual(start_progress, final_progress))
return;
// Create the animator and set up its completion block.
animator_ = [[FullscreenAnimator alloc] initWithStartProgress:start_progress
style:style];
base::WeakPtr<FullscreenMediator> weak_mediator = weak_factory_.GetWeakPtr();
[animator_ addAnimations:^{
// Updates the WebView frame during the animation to have it animated.
FullscreenMediator* mediator = weak_mediator.get();
if (mediator)
[mediator->resizer_ forceToUpdateToProgress:final_progress];
}];
[animator_ addCompletion:^(UIViewAnimatingPosition finalPosition) {
DCHECK_EQ(finalPosition, UIViewAnimatingPositionEnd);
FullscreenMediator* mediator = weak_mediator.get();
if (!mediator)
return;
mediator->model_->AnimationEndedWithProgress(final_progress);
mediator->animator_ = nil;
}];
// Notify observers that the animation will occur.
for (auto& observer : observers_) {
observer.FullscreenWillAnimate(controller_, animator_);
}
// Only start the animator if animations have been added.
if (animator_.hasAnimations) {
[animator_ startAnimation];
} else {
animator_ = nil;
}
}
void FullscreenMediator::StopAnimating(bool update_model) {
if (!animator_)
return;
DCHECK_EQ(animator_.state, UIViewAnimatingStateActive);
if (update_model)
model_->AnimationEndedWithProgress(animator_.currentProgress);
[animator_ stopAnimation:YES];
animator_ = nil;
}
void FullscreenMediator::ResizeHorizontalInsets() {
for (auto& observer : observers_) {
observer.ResizeHorizontalInsets(controller_);
}
}