#ifndef COMPONENTS_USER_EDUCATION_COMMON_TUTORIAL_DESCRIPTION_H_
#define COMPONENTS_USER_EDUCATION_COMMON_TUTORIAL_DESCRIPTION_H_
#include <string>
#include <vector>
#include "base/metrics/histogram_macros.h"
#include "components/user_education/common/help_bubble.h"
#include "components/user_education/common/help_bubble_params.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/interaction/interaction_sequence.h"
namespace user_education {
class TutorialHistograms {
public:
TutorialHistograms() = default;
TutorialHistograms(const TutorialHistograms& other) = delete;
virtual ~TutorialHistograms() = default;
void operator=(const TutorialHistograms& other) = delete;
virtual void RecordComplete(bool value) = 0;
virtual void RecordAbortStep(int step) = 0;
virtual void RecordIphLinkClicked(bool value) = 0;
virtual void RecordStartedFromWhatsNewPage(bool value) = 0;
virtual const std::string& GetTutorialPrefix() const = 0;
};
namespace internal {
constexpr char kTutorialHistogramPrefix[] = "Tutorial.";
template <const char histogram_name[]>
class TutorialHistogramsImpl : public TutorialHistograms {
public:
explicit TutorialHistogramsImpl(int max_steps)
: histogram_name_(histogram_name),
completed_name_(kTutorialHistogramPrefix + histogram_name_ +
".Completion"),
aborted_name_(kTutorialHistogramPrefix + histogram_name_ +
".AbortStep"),
iph_link_clicked_name_(kTutorialHistogramPrefix + histogram_name_ +
".IPHLinkClicked"),
whats_new_page_name_(kTutorialHistogramPrefix + histogram_name_ +
".StartedFromWhatsNewPage"),
max_steps_(max_steps) {}
~TutorialHistogramsImpl() override = default;
protected:
void RecordComplete(bool value) override {
UMA_HISTOGRAM_BOOLEAN(completed_name_, value);
}
void RecordAbortStep(int step) override {
UMA_HISTOGRAM_EXACT_LINEAR(aborted_name_, step, max_steps_);
}
void RecordIphLinkClicked(bool value) override {
UMA_HISTOGRAM_BOOLEAN(iph_link_clicked_name_, value);
}
void RecordStartedFromWhatsNewPage(bool value) override {
UMA_HISTOGRAM_BOOLEAN(whats_new_page_name_, value);
}
const std::string& GetTutorialPrefix() const override {
return histogram_name_;
}
private:
const std::string histogram_name_;
const std::string completed_name_;
const std::string aborted_name_;
const std::string iph_link_clicked_name_;
const std::string whats_new_page_name_;
const int max_steps_;
};
}
template <const char* histogram_name>
std::unique_ptr<TutorialHistograms> MakeTutorialHistograms(int max_steps) {
return std::make_unique<internal::TutorialHistogramsImpl<histogram_name>>(
max_steps);
}
struct TutorialDescription {
using NameElementsCallback =
base::RepeatingCallback<bool(ui::InteractionSequence*,
ui::TrackedElement*)>;
using NextButtonCallback =
base::RepeatingCallback<void(ui::TrackedElement* current_anchor)>;
TutorialDescription();
~TutorialDescription();
TutorialDescription(TutorialDescription&& other);
TutorialDescription& operator=(TutorialDescription&& other);
using ContextMode = ui::InteractionSequence::ContextMode;
using ElementSpecifier = absl::variant<ui::ElementIdentifier, std::string>;
struct Step {
Step();
explicit Step(
ElementSpecifier element_specifier,
ui::InteractionSequence::StepType step_type_ =
ui::InteractionSequence::StepType::kShown,
ui::CustomElementEventType event_type_ = ui::CustomElementEventType());
Step(int title_text_id_,
int body_text_id_,
ui::InteractionSequence::StepType step_type_,
ui::ElementIdentifier element_id_,
std::string element_name_,
HelpBubbleArrow arrow_,
ui::CustomElementEventType event_type_ = ui::CustomElementEventType(),
absl::optional<bool> must_remain_visible_ = absl::nullopt,
bool transition_only_on_event_ = false,
NameElementsCallback name_elements_callback_ = NameElementsCallback(),
ContextMode step_context = ContextMode::kInitial);
Step(const Step& other);
Step& operator=(const Step& other);
~Step();
ui::ElementIdentifier element_id;
std::string element_name;
ui::InteractionSequence::StepType step_type =
ui::InteractionSequence::StepType::kShown;
ui::CustomElementEventType event_type = ui::CustomElementEventType();
int title_text_id = 0;
int body_text_id = 0;
HelpBubbleArrow arrow = HelpBubbleArrow::kTopRight;
absl::optional<bool> must_remain_visible = absl::nullopt;
bool transition_only_on_event = false;
absl::optional<bool> must_be_visible;
NameElementsCallback name_elements_callback = NameElementsCallback();
ContextMode context_mode = ContextMode::kInitial;
NextButtonCallback next_button_callback = NextButtonCallback();
bool ShouldShowBubble() const;
Step& AbortIfVisibilityLost(bool must_remain_visible_) {
must_remain_visible = must_remain_visible_;
return *this;
}
Step& AbortIfNotVisible() {
must_be_visible = true;
return *this;
}
Step& NameElement(const char name_[]) {
return NameElements(base::BindRepeating(
[](const char name[], ui::InteractionSequence* sequence,
ui::TrackedElement* element) {
sequence->NameElement(element, base::StringPiece(name));
return true;
},
name_));
}
Step& NameElements(NameElementsCallback name_elements_callback_) {
name_elements_callback = std::move(name_elements_callback_);
return *this;
}
Step& InAnyContext() {
context_mode = ContextMode::kAny;
return *this;
}
Step& InSameContext() {
context_mode = ContextMode::kFromPreviousStep;
return *this;
}
};
struct BubbleStep : public Step {
explicit BubbleStep(ElementSpecifier element_specifier)
: Step(element_specifier, ui::InteractionSequence::StepType::kShown) {}
BubbleStep& SetBubbleTitleText(int title_text_) {
title_text_id = title_text_;
return *this;
}
BubbleStep& SetBubbleBodyText(int body_text_) {
body_text_id = body_text_;
return *this;
}
BubbleStep& SetBubbleArrow(HelpBubbleArrow arrow_) {
arrow = arrow_;
return *this;
}
BubbleStep& AddDefaultNextButton() {
return AddCustomNextButton(
base::BindRepeating([](ui::TrackedElement* current_anchor) {
ui::ElementTracker::GetFrameworkDelegate()->NotifyCustomEvent(
current_anchor, kHelpBubbleNextButtonClickedEvent);
}));
}
BubbleStep& AddCustomNextButton(NextButtonCallback next_button_callback_) {
next_button_callback = std::move(next_button_callback_);
return *this;
}
};
struct HiddenStep : public Step {
static HiddenStep WaitForShowEvent(ElementSpecifier element_specifier) {
HiddenStep step(element_specifier,
ui::InteractionSequence::StepType::kShown);
step.transition_only_on_event = true;
return step;
}
static HiddenStep WaitForHideEvent(ElementSpecifier element_specifier) {
HiddenStep step(element_specifier,
ui::InteractionSequence::StepType::kHidden);
step.transition_only_on_event = true;
return step;
}
static HiddenStep WaitForActivateEvent(ElementSpecifier element_specifier) {
HiddenStep step(element_specifier,
ui::InteractionSequence::StepType::kActivated);
step.transition_only_on_event = true;
return step;
}
static HiddenStep WaitForShown(ElementSpecifier element_specifier) {
HiddenStep step(element_specifier,
ui::InteractionSequence::StepType::kShown);
step.transition_only_on_event = false;
return step;
}
static HiddenStep WaitForHidden(ElementSpecifier element_specifier) {
HiddenStep step(element_specifier,
ui::InteractionSequence::StepType::kHidden);
step.transition_only_on_event = false;
return step;
}
static HiddenStep WaitForActivated(ElementSpecifier element_specifier) {
HiddenStep step(element_specifier,
ui::InteractionSequence::StepType::kActivated);
step.transition_only_on_event = false;
return step;
}
private:
explicit HiddenStep(ElementSpecifier element_specifier,
ui::InteractionSequence::StepType step_type)
: Step(element_specifier, step_type) {}
};
struct EventStep : public Step {
explicit EventStep(ui::CustomElementEventType event_type_)
: Step(ui::ElementIdentifier(),
ui::InteractionSequence::StepType::kCustomEvent,
event_type_) {}
EventStep(ui::CustomElementEventType event_type_,
ElementSpecifier element_specifier)
: Step(element_specifier,
ui::InteractionSequence::StepType::kCustomEvent,
event_type_) {}
};
template <const char histogram_name[], typename... Args>
static TutorialDescription Create(Args&&... steps) {
TutorialDescription description;
description.steps = Steps(steps...);
description.histograms =
user_education::MakeTutorialHistograms<histogram_name>(
description.steps.size());
return description;
}
template <typename... Args>
static std::vector<TutorialDescription::Step> Steps(Args&&... steps) {
std::vector<TutorialDescription::Step> flat_steps = {};
(AddStep(flat_steps, std::forward<Args>(steps)), ...);
return flat_steps;
}
std::vector<Step> steps;
std::unique_ptr<TutorialHistograms> histograms;
bool can_be_restarted = false;
private:
static void AddStep(std::vector<Step>& dest, Step step) {
dest.emplace_back(step);
}
static void AddStep(std::vector<Step>& dest, const std::vector<Step>& src) {
for (auto& step : src) {
dest.emplace_back(step);
}
}
};
}
#endif