#ifndef COMPONENTS_USER_EDUCATION_COMMON_TUTORIAL_TUTORIAL_DESCRIPTION_H_
#define COMPONENTS_USER_EDUCATION_COMMON_TUTORIAL_TUTORIAL_DESCRIPTION_H_
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_macros.h"
#include "components/strings/grit/components_strings.h"
#include "components/user_education/common/help_bubble/help_bubble_params.h"
#include "components/user_education/common/user_education_metadata.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_specifier.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;
void operator=(const TutorialHistograms& other) = delete;
virtual ~TutorialHistograms() = default;
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);
}
class ScopedTutorialState {
public:
explicit ScopedTutorialState(ui::ElementContext context);
virtual ~ScopedTutorialState();
ui::ElementContext context() const { return context_; }
private:
const ui::ElementContext context_;
};
struct TutorialDescription {
using NameElementsCallback =
base::RepeatingCallback<bool(ui::InteractionSequence*,
ui::TrackedElement*)>;
using NextButtonCallback =
base::RepeatingCallback<void(ui::TrackedElement* current_anchor)>;
using TemporaryStateCallback =
base::RepeatingCallback<std::unique_ptr<ScopedTutorialState>(
ui::ElementContext)>;
TutorialDescription();
TutorialDescription(TutorialDescription&& other) noexcept;
TutorialDescription& operator=(TutorialDescription&& other) noexcept;
~TutorialDescription();
using ContextMode = ui::InteractionSequence::ContextMode;
using ElementSpecifier = ui::ElementSpecifier;
using ConditionalCallback =
base::RepeatingCallback<bool(const ui::TrackedElement* element)>;
class Step {
public:
Step();
Step(const Step& other);
Step& operator=(const Step& other);
~Step();
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(std::string 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;
}
ui::ElementIdentifier element_id() const { return element_.identifier(); }
std::string_view element_name() const { return element_.name(); }
ElementSpecifier element() const { return element_; }
ui::InteractionSequence::StepType step_type() const { return step_type_; }
ui::CustomElementEventType event_type() const { return event_type_; }
int title_text_id() const { return title_text_id_; }
int body_text_id() const { return body_text_id_; }
int screenreader_text_id() const { return screenreader_text_id_; }
HelpBubbleArrow arrow() const { return arrow_; }
std::optional<bool> must_remain_visible() const {
return must_remain_visible_;
}
std::optional<bool> must_be_visible() const { return must_be_visible_; }
bool transition_only_on_event() const { return transition_only_on_event_; }
const NameElementsCallback& name_elements_callback() const {
return name_elements_callback_;
}
ContextMode context_mode() const { return context_mode_; }
const NextButtonCallback& next_button_callback() const {
return next_button_callback_;
}
const HelpBubbleParams::ExtendedProperties& extended_properties() const {
return extended_properties_;
}
ui::InteractionSequence::SubsequenceMode subsequence_mode() const {
return subsequence_mode_;
}
const auto& branches() const { return branches_; }
protected:
Step(ElementSpecifier element,
ui::InteractionSequence::StepType step_type,
HelpBubbleArrow arrow = HelpBubbleArrow::kNone,
ui::CustomElementEventType event_type = ui::CustomElementEventType());
ElementSpecifier element_;
ui::InteractionSequence::StepType step_type_ =
ui::InteractionSequence::StepType::kShown;
ui::CustomElementEventType event_type_;
int title_text_id_ = 0;
int body_text_id_ = 0;
int screenreader_text_id_ = 0;
HelpBubbleArrow arrow_ = HelpBubbleArrow::kNone;
std::optional<bool> must_remain_visible_ = std::nullopt;
std::optional<bool> must_be_visible_;
bool transition_only_on_event_ = false;
NameElementsCallback name_elements_callback_ = NameElementsCallback();
ContextMode context_mode_ = ContextMode::kInitial;
NextButtonCallback next_button_callback_ = NextButtonCallback();
HelpBubbleParams::ExtendedProperties extended_properties_;
ui::InteractionSequence::SubsequenceMode subsequence_mode_ =
ui::InteractionSequence::SubsequenceMode::kAtMostOne;
std::vector<std::pair<ConditionalCallback, std::vector<Step>>> branches_;
private:
friend class Tutorial;
};
class BubbleStep : public Step {
public:
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_id) {
body_text_id_ = body_text_id;
return *this;
}
BubbleStep& SetBubbleScreenreaderText(int screenreader_text_id) {
screenreader_text_id_ = screenreader_text_id;
return *this;
}
BubbleStep& SetBubbleArrow(HelpBubbleArrow arrow) {
arrow_ = arrow;
return *this;
}
BubbleStep& SetExtendedProperties(
HelpBubbleParams::ExtendedProperties extended_properties) {
extended_properties_ = std::move(extended_properties);
return *this;
}
BubbleStep& AddCustomNextButton(NextButtonCallback next_button_callback) {
next_button_callback_ = std::move(next_button_callback);
return *this;
}
BubbleStep& AddDefaultNextButton();
};
class HiddenStep : public Step {
public:
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 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) {
return HiddenStep(element_specifier,
ui::InteractionSequence::StepType::kActivated);
}
template <typename... Args>
static HiddenStep WaitForOneOf(Args&&... args) {}
protected:
explicit HiddenStep(ElementSpecifier element_specifier,
ui::InteractionSequence::StepType step_type)
: Step(element_specifier, step_type) {}
};
class EventStep : public Step {
public:
explicit EventStep(ui::CustomElementEventType event_type)
: Step(ui::ElementIdentifier(),
ui::InteractionSequence::StepType::kCustomEvent,
HelpBubbleArrow::kNone,
event_type) {}
EventStep(ui::CustomElementEventType event_type,
ElementSpecifier element_specifier)
: Step(element_specifier,
ui::InteractionSequence::StepType::kCustomEvent,
HelpBubbleArrow::kNone,
event_type) {}
};
template <const char histogram_name[], typename... Args>
static TutorialDescription Create(Args... steps) {
TutorialDescription description;
description.steps = Steps(std::move(steps)...);
description.histograms =
user_education::MakeTutorialHistograms<histogram_name>(
description.steps.size());
return description;
}
template <typename... Args>
static std::vector<Step> Steps(Args... steps) {
std::vector<Step> flat_steps;
(AddStep(flat_steps, std::move(steps)), ...);
return flat_steps;
}
class If : public Step {
public:
explicit If(ElementSpecifier element,
ConditionalCallback if_condition = RunIfPresent())
: Step(element, ui::InteractionSequence::StepType::kSubsequence) {
branches_.emplace_back(std::move(if_condition), std::vector<Step>());
}
template <typename... Args>
If& Then(Args... then_steps) {
CHECK_EQ(1U, branches_.size());
CHECK(branches_[0].second.empty());
branches_[0].second = Steps(std::move(then_steps)...);
return *this;
}
template <typename... Args>
If& Else(Args... else_steps) {
CHECK_EQ(1U, branches_.size());
branches_.emplace_back(AlwaysRun(), Steps(std::move(else_steps)...));
subsequence_mode_ = ui::InteractionSequence::SubsequenceMode::kExactlyOne;
return *this;
}
using Step::branches;
auto& branches() { return branches_; }
};
class WaitForAnyOf : public HiddenStep {
public:
explicit WaitForAnyOf(ElementSpecifier first, bool wait_for_event = false);
WaitForAnyOf& Or(ElementSpecifier element, bool wait_for_event = false);
};
std::vector<Step> steps;
std::unique_ptr<TutorialHistograms> histograms;
TemporaryStateCallback temporary_state_callback;
bool can_be_restarted = false;
int complete_button_text_id = IDS_TUTORIAL_CLOSE_TUTORIAL;
Metadata metadata;
private:
static void AddStep(std::vector<Step>& dest, Step step) {
dest.emplace_back(std::move(step));
}
static void AddStep(std::vector<Step>& dest, const std::vector<Step>& src) {
for (auto& step : src) {
dest.emplace_back(step);
}
}
static ConditionalCallback AlwaysRun();
static ConditionalCallback RunIfPresent();
};
}
#endif