// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/assistant/ui/main_stage/assistant_footer_view.h"
#include <utility>
#include "ash/assistant/ui/assistant_ui_constants.h"
#include "ash/assistant/ui/assistant_view_delegate.h"
#include "ash/assistant/ui/assistant_view_ids.h"
#include "ash/assistant/ui/main_stage/assistant_opt_in_view.h"
#include "ash/assistant/ui/main_stage/suggestion_container_view.h"
#include "ash/assistant/util/animation_util.h"
#include "ash/constants/ash_features.h"
#include "base/functional/bind.h"
#include "base/time/time.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/callback_layer_animation_observer.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_element.h"
#include "ui/compositor/layer_animator.h"
#include "ui/views/layout/fill_layout.h"
namespace ash {
namespace {
// Animation.
constexpr base::TimeDelta kAnimationFadeInDelay = base::Milliseconds(167);
constexpr base::TimeDelta kAnimationFadeInDuration = base::Milliseconds(167);
constexpr base::TimeDelta kAnimationFadeOutDuration = base::Milliseconds(167);
constexpr int kPreferredHeightDip = 64;
} // namespace
AssistantFooterView::AssistantFooterView(AssistantViewDelegate* delegate)
: delegate_(delegate),
animation_observer_(std::make_unique<ui::CallbackLayerAnimationObserver>(
/*animation_started_callback=*/base::BindRepeating(
&AssistantFooterView::OnAnimationStarted,
base::Unretained(this)),
/*animation_ended_callback=*/base::BindRepeating(
&AssistantFooterView::OnAnimationEnded,
base::Unretained(this)))) {
SetID(AssistantViewID::kFooterView);
InitLayout();
AssistantState::Get()->AddObserver(this);
}
AssistantFooterView::~AssistantFooterView() {
AssistantState::Get()->RemoveObserver(this);
}
gfx::Size AssistantFooterView::CalculatePreferredSize(
const views::SizeBounds& available_size) const {
return gfx::Size(INT_MAX, kPreferredHeightDip);
}
void AssistantFooterView::InitLayout() {
SetLayoutManager(std::make_unique<views::FillLayout>());
// Initial view state is based on user consent state.
const bool consent_given =
AssistantState::Get()->consent_status().value_or(
assistant::prefs::ConsentStatus::kUnknown) ==
assistant::prefs::ConsentStatus::kActivityControlAccepted;
// Suggestion container.
suggestion_container_ =
AddChildView(std::make_unique<SuggestionContainerView>(delegate_));
suggestion_container_->SetCanProcessEventsWithinSubtree(consent_given);
// Suggestion container will be animated on its own layer.
suggestion_container_->SetPaintToLayer();
suggestion_container_->layer()->SetFillsBoundsOpaquely(false);
suggestion_container_->layer()->SetOpacity(consent_given ? 1.f : 0.f);
suggestion_container_->SetVisible(consent_given);
// Opt in view.
opt_in_view_ = AddChildView(std::make_unique<AssistantOptInView>(delegate_));
opt_in_view_->SetCanProcessEventsWithinSubtree(!consent_given);
// Opt in view will be animated on its own layer.
opt_in_view_->SetPaintToLayer();
opt_in_view_->layer()->SetFillsBoundsOpaquely(false);
opt_in_view_->layer()->SetOpacity(consent_given ? 0.f : 1.f);
opt_in_view_->SetVisible(!consent_given);
}
void AssistantFooterView::OnAssistantConsentStatusChanged(int consent_status) {
using assistant::util::CreateLayerAnimationSequence;
using assistant::util::CreateOpacityElement;
using assistant::util::StartLayerAnimationSequence;
const bool consent_given =
consent_status ==
assistant::prefs::ConsentStatus::kActivityControlAccepted;
// When the consent state changes, we need to hide/show the appropriate views.
views::View* hide_view =
consent_given ? static_cast<views::View*>(opt_in_view_)
: static_cast<views::View*>(suggestion_container_);
views::View* show_view =
consent_given ? static_cast<views::View*>(suggestion_container_)
: static_cast<views::View*>(opt_in_view_);
// Reset visibility to enable animation.
hide_view->SetVisible(true);
show_view->SetVisible(true);
// Hide the view for the previous consent state by fading to 0% opacity.
StartLayerAnimationSequence(hide_view->layer()->GetAnimator(),
CreateLayerAnimationSequence(CreateOpacityElement(
0.f, kAnimationFadeOutDuration)),
// Observe the animation.
animation_observer_.get());
// Show the view for the next consent state by fading to 100% opacity with
// delay.
StartLayerAnimationSequence(
show_view->layer()->GetAnimator(),
CreateLayerAnimationSequence(
ui::LayerAnimationElement::CreatePauseElement(
ui::LayerAnimationElement::AnimatableProperty::OPACITY,
kAnimationFadeInDelay),
CreateOpacityElement(1.f, kAnimationFadeInDuration)),
// Observe the animation.
animation_observer_.get());
// Set the observer to active to receive animation callback events.
animation_observer_->SetActive();
}
void AssistantFooterView::InitializeUIForBubbleView() {
suggestion_container_->InitializeUIForBubbleView();
}
void AssistantFooterView::OnAnimationStarted(
const ui::CallbackLayerAnimationObserver& observer) {
// Our views should not process events while animating.
suggestion_container_->SetCanProcessEventsWithinSubtree(false);
opt_in_view_->SetCanProcessEventsWithinSubtree(false);
}
bool AssistantFooterView::OnAnimationEnded(
const ui::CallbackLayerAnimationObserver& observer) {
const bool consent_given =
AssistantState::Get()->consent_status().value_or(
assistant::prefs::ConsentStatus::kUnknown) ==
assistant::prefs::ConsentStatus::kActivityControlAccepted;
// Only the view relevant to our consent state should process events.
suggestion_container_->SetCanProcessEventsWithinSubtree(consent_given);
suggestion_container_->SetVisible(consent_given);
opt_in_view_->SetCanProcessEventsWithinSubtree(!consent_given);
opt_in_view_->SetVisible(!consent_given);
// Return false to prevent the observer from destroying itself.
return false;
}
BEGIN_METADATA(AssistantFooterView)
END_METADATA
} // namespace ash