blob: 7585fe97f335653cbaa766c61633f066f55824b1 [file] [log] [blame]
// Copyright 2020 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.
#include "ash/assistant/ui/main_stage/assistant_onboarding_view.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "ash/assistant/model/assistant_suggestions_model.h"
#include "ash/assistant/model/assistant_ui_model.h"
#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/util/resource_util.h"
#include "ash/public/cpp/assistant/controller/assistant_suggestions_controller.h"
#include "ash/public/cpp/assistant/controller/assistant_ui_controller.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/color_palette.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/view_class_properties.h"
namespace ash {
namespace {
using assistant::util::ResourceLinkType;
using chromeos::assistant::AssistantSuggestion;
using chromeos::assistant::AssistantSuggestionType;
constexpr int kHorizontalMarginDip = 56;
// Greeting.
constexpr int kGreetingLabelLineHeight = 24;
constexpr int kGreetingLabelSizeDelta = 8;
// Intro.
constexpr int kIntroLabelLineHeight = 22;
constexpr int kIntroLabelMarginTopDip = 12;
constexpr int kIntroLabelSizeDelta = 3;
// Suggestions.
constexpr int kSuggestionsColumnCount = 3;
constexpr int kSuggestionsColumnSetId = 1;
constexpr int kSuggestionsCornerRadiusDip = 16;
constexpr int kSuggestionsIconSizeDip = 24;
constexpr int kSuggestionsLabelLineHeight = 20;
constexpr int kSuggestionsLabelSizeDelta = 2;
constexpr int kSuggestionsMaxCount = 6;
constexpr int kSuggestionsMarginDip = 16;
constexpr int kSuggestionsMarginTopDip = 48;
constexpr int kSuggestionsPaddingDip = 16;
constexpr int kSuggestionsPreferredHeightDip = 72;
constexpr int kSuggestionsSpacingDip = 12;
// Helpers ---------------------------------------------------------------------
std::string GetGreetingMessage(AssistantViewDelegate* delegate) {
base::Time::Exploded now;
base::Time::Now().LocalExplode(&now);
if (now.hour < 5) {
return l10n_util::GetStringFUTF8(
IDS_ASSISTANT_BETTER_ONBOARDING_GREETING_NIGHT,
base::UTF8ToUTF16(delegate->GetPrimaryUserGivenName()));
}
if (now.hour < 12) {
return l10n_util::GetStringFUTF8(
IDS_ASSISTANT_BETTER_ONBOARDING_GREETING_MORNING,
base::UTF8ToUTF16(delegate->GetPrimaryUserGivenName()));
}
if (now.hour < 17) {
return l10n_util::GetStringFUTF8(
IDS_ASSISTANT_BETTER_ONBOARDING_GREETING_AFTERNOON,
base::UTF8ToUTF16(delegate->GetPrimaryUserGivenName()));
}
if (now.hour < 23) {
return l10n_util::GetStringFUTF8(
IDS_ASSISTANT_BETTER_ONBOARDING_GREETING_EVENING,
base::UTF8ToUTF16(delegate->GetPrimaryUserGivenName()));
}
return l10n_util::GetStringFUTF8(
IDS_ASSISTANT_BETTER_ONBOARDING_GREETING_NIGHT,
base::UTF8ToUTF16(delegate->GetPrimaryUserGivenName()));
}
SkColor GetSuggestionBackgroundColor(int index) {
DCHECK_GE(index, 0);
DCHECK_LT(index, kSuggestionsMaxCount);
constexpr SkColor background_colors[kSuggestionsMaxCount] = {
gfx::kGoogleBlue050, gfx::kGoogleYellow050, gfx::kGoogleGreen050,
gfx::kGoogleYellow050, gfx::kGoogleGreen050, gfx::kGoogleRed050};
return background_colors[index];
}
SkColor GetSuggestionForegroundColor(int index) {
DCHECK_GE(index, 0);
DCHECK_LT(index, kSuggestionsMaxCount);
constexpr SkColor foreground_colors[kSuggestionsMaxCount] = {
gfx::kGoogleBlue700, gfx::kGoogleYellow900, gfx::kGoogleGreen800,
gfx::kGoogleYellow900, gfx::kGoogleGreen800, gfx::kGoogleRed800};
return foreground_colors[index];
}
// SuggestionView --------------------------------------------------------------
class SuggestionView : public views::Button, public views::ButtonListener {
public:
SuggestionView(AssistantViewDelegate* delegate,
const AssistantSuggestion& suggestion,
int index)
: views::Button(this),
delegate_(delegate),
suggestion_id_(suggestion.id),
index_(index) {
InitLayout(suggestion);
}
SuggestionView(const SuggestionView&) = delete;
SuggestionView& operator=(const SuggestionView&) = delete;
~SuggestionView() override = default;
// views::Button:
const char* GetClassName() const override { return "SuggestionView"; }
int GetHeightForWidth(int width) const override {
return kSuggestionsPreferredHeightDip;
}
void ChildPreferredSizeChanged(views::View* child) override {
PreferredSizeChanged();
}
// views::ButtonListener:
void ButtonPressed(views::Button* sender, const ui::Event& event) override {
delegate_->OnSuggestionPressed(suggestion_id_);
}
private:
void InitLayout(const AssistantSuggestion& suggestion) {
// Background.
SetBackground(views::CreateRoundedRectBackground(
GetSuggestionBackgroundColor(index_), kSuggestionsCornerRadiusDip));
// Layout.
SetLayoutManager(std::make_unique<views::FlexLayout>())
->SetCollapseMargins(true)
.SetCrossAxisAlignment(views::LayoutAlignment::kCenter)
.SetDefault(views::kFlexBehaviorKey, views::FlexSpecification())
.SetDefault(views::kMarginsKey, gfx::Insets(0, kSuggestionsSpacingDip))
.SetInteriorMargin(gfx::Insets(0, kSuggestionsPaddingDip))
.SetOrientation(views::LayoutOrientation::kHorizontal);
// Icon.
icon_ = AddChildView(std::make_unique<views::ImageView>());
icon_->SetImageSize({kSuggestionsIconSizeDip, kSuggestionsIconSizeDip});
icon_->SetPreferredSize({kSuggestionsIconSizeDip, kSuggestionsIconSizeDip});
const GURL& url = suggestion.icon_url;
if (assistant::util::IsResourceLinkType(url, ResourceLinkType::kIcon)) {
// Handle local images.
icon_->SetImage(assistant::util::CreateVectorIcon(
assistant::util::AppendOrReplaceColorParam(
url, GetSuggestionForegroundColor(index_)),
kSuggestionsIconSizeDip));
} else if (url.is_valid()) {
// Handle remote images.
delegate_->DownloadImage(url,
base::BindOnce(&SuggestionView::OnIconDownloaded,
weak_factory_.GetWeakPtr()));
}
// Label.
label_ = AddChildView(std::make_unique<views::Label>());
label_->SetAutoColorReadabilityEnabled(false);
label_->SetEnabledColor(GetSuggestionForegroundColor(index_));
label_->SetFontList(assistant::ui::GetDefaultFontList().DeriveWithSizeDelta(
kSuggestionsLabelSizeDelta));
label_->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
label_->SetLineHeight(kSuggestionsLabelLineHeight);
label_->SetMaxLines(2);
label_->SetMultiLine(true);
label_->SetPreferredSize(gfx::Size(INT_MAX, INT_MAX));
label_->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kUnbounded,
/*adjust_height_for_width=*/true));
label_->SetText(base::UTF8ToUTF16(suggestion.text));
}
void OnIconDownloaded(const gfx::ImageSkia& icon) {
if (!icon.isNull())
icon_->SetImage(icon);
}
AssistantViewDelegate* const delegate_;
const base::UnguessableToken suggestion_id_;
const int index_;
views::ImageView* icon_; // Owned by view hierarchy.
views::Label* label_; // Owned by view hierarchy.
base::WeakPtrFactory<SuggestionView> weak_factory_{this};
};
} // namespace
// AssistantOnboardingView -----------------------------------------------------
AssistantOnboardingView::AssistantOnboardingView(
AssistantViewDelegate* delegate)
: delegate_(delegate) {
SetID(AssistantViewID::kOnboardingView);
InitLayout();
assistant_controller_observer_.Add(AssistantController::Get());
AssistantSuggestionsController::Get()->GetModel()->AddObserver(this);
AssistantUiController::Get()->GetModel()->AddObserver(this);
}
AssistantOnboardingView::~AssistantOnboardingView() {
if (AssistantUiController::Get())
AssistantUiController::Get()->GetModel()->RemoveObserver(this);
if (AssistantSuggestionsController::Get())
AssistantSuggestionsController::Get()->GetModel()->RemoveObserver(this);
}
const char* AssistantOnboardingView::GetClassName() const {
return "AssistantOnboardingView";
}
gfx::Size AssistantOnboardingView::CalculatePreferredSize() const {
return gfx::Size(INT_MAX, GetHeightForWidth(INT_MAX));
}
void AssistantOnboardingView::ChildPreferredSizeChanged(views::View* child) {
PreferredSizeChanged();
}
void AssistantOnboardingView::OnAssistantControllerDestroying() {
AssistantUiController::Get()->GetModel()->RemoveObserver(this);
AssistantSuggestionsController::Get()->GetModel()->RemoveObserver(this);
assistant_controller_observer_.Remove(AssistantController::Get());
}
void AssistantOnboardingView::OnOnboardingSuggestionsChanged(
const std::vector<AssistantSuggestion>& onboarding_suggestions) {
UpdateSuggestions();
}
void AssistantOnboardingView::OnUiVisibilityChanged(
AssistantVisibility new_visibility,
AssistantVisibility old_visibility,
base::Optional<AssistantEntryPoint> entry_point,
base::Optional<AssistantExitPoint> exit_point) {
if (new_visibility == AssistantVisibility::kVisible)
UpdateGreeting();
}
void AssistantOnboardingView::InitLayout() {
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
gfx::Insets(0, kHorizontalMarginDip)));
// Greeting.
greeting_ = AddChildView(std::make_unique<views::Label>());
greeting_->SetAutoColorReadabilityEnabled(false);
greeting_->SetBackground(views::CreateSolidBackground(SK_ColorWHITE));
greeting_->SetEnabledColor(SK_ColorBLACK);
greeting_->SetFontList(assistant::ui::GetDefaultFontList()
.DeriveWithSizeDelta(kGreetingLabelSizeDelta)
.DeriveWithWeight(gfx::Font::Weight::MEDIUM));
greeting_->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
greeting_->SetLineHeight(kGreetingLabelLineHeight);
greeting_->SetText(base::UTF8ToUTF16(GetGreetingMessage(delegate_)));
// Intro.
auto intro = std::make_unique<views::Label>();
intro->SetAutoColorReadabilityEnabled(false);
intro->SetBackground(views::CreateSolidBackground(SK_ColorWHITE));
intro->SetBorder(views::CreateEmptyBorder(kIntroLabelMarginTopDip, 0, 0, 0));
intro->SetEnabledColor(gfx::kGoogleGrey900);
intro->SetFontList(assistant::ui::GetDefaultFontList()
.DeriveWithSizeDelta(kIntroLabelSizeDelta)
.DeriveWithWeight(gfx::Font ::Weight::MEDIUM));
intro->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
intro->SetLineHeight(kIntroLabelLineHeight);
intro->SetMultiLine(true);
intro->SetText(
l10n_util::GetStringUTF16(IDS_ASSISTANT_BETTER_ONBOARDING_INTRO));
AddChildView(std::move(intro));
// Suggestions.
UpdateSuggestions();
}
void AssistantOnboardingView::UpdateSuggestions() {
if (grid_)
RemoveChildViewT(grid_);
grid_ = AddChildView(std::make_unique<views::View>());
grid_->SetBorder(views::CreateEmptyBorder(kSuggestionsMarginTopDip, 0, 0, 0));
auto* layout = grid_->SetLayoutManager(std::make_unique<views::GridLayout>());
auto* columns = layout->AddColumnSet(kSuggestionsColumnSetId);
// Initialize columns.
for (int i = 0; i < kSuggestionsColumnCount; ++i) {
if (i > 0) {
columns->AddPaddingColumn(
/*resize_percent=*/views::GridLayout::kFixedSize,
/*width=*/kSuggestionsMarginDip);
}
columns->AddColumn(
/*h_align=*/views::GridLayout::Alignment::FILL,
/*v_align=*/views::GridLayout::Alignment::FILL, /*resize_percent=*/1.0,
/*size_type=*/views::GridLayout::ColumnSize::kFixed,
/*fixed_width=*/0, /*min_width=*/0);
}
const std::vector<AssistantSuggestion>& suggestions =
AssistantSuggestionsController::Get()
->GetModel()
->GetOnboardingSuggestions();
// Initialize suggestions.
for (size_t i = 0; i < suggestions.size() && i < kSuggestionsMaxCount; ++i) {
if (i % kSuggestionsColumnCount == 0) {
if (i > 0) {
layout->StartRowWithPadding(
/*vertical_resize=*/views::GridLayout::kFixedSize,
/*column_set_id=*/kSuggestionsColumnSetId,
/*padding_resize=*/views::GridLayout::kFixedSize,
/*padding=*/kSuggestionsMarginDip);
} else {
layout->StartRow(/*vertical_resize=*/views::GridLayout::kFixedSize,
/*column_set_id=*/kSuggestionsColumnSetId);
}
}
layout->AddView(
std::make_unique<SuggestionView>(delegate_, suggestions.at(i), i));
}
}
void AssistantOnboardingView::UpdateGreeting() {
greeting_->SetText(base::UTF8ToUTF16(GetGreetingMessage(delegate_)));
}
} // namespace ash