tutorial.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/user_education/common/tutorial.h"
  5. #include "base/bind.h"
  6. #include "base/metrics/histogram_macros.h"
  7. #include "base/time/time.h"
  8. #include "components/strings/grit/components_strings.h"
  9. #include "components/user_education/common/help_bubble.h"
  10. #include "components/user_education/common/help_bubble_factory.h"
  11. #include "components/user_education/common/help_bubble_factory_registry.h"
  12. #include "components/user_education/common/help_bubble_params.h"
  13. #include "components/user_education/common/tutorial_description.h"
  14. #include "components/user_education/common/tutorial_service.h"
  15. #include "components/vector_icons/vector_icons.h"
  16. #include "third_party/abseil-cpp/absl/types/optional.h"
  17. #include "ui/base/interaction/element_identifier.h"
  18. #include "ui/base/interaction/element_tracker.h"
  19. #include "ui/base/interaction/interaction_sequence.h"
  20. #include "ui/base/l10n/l10n_util.h"
  21. namespace user_education {
  22. Tutorial::StepBuilder::StepBuilder() = default;
  23. Tutorial::StepBuilder::StepBuilder(const TutorialDescription::Step& step)
  24. : step_(step) {}
  25. Tutorial::StepBuilder::~StepBuilder() = default;
  26. // static
  27. std::unique_ptr<ui::InteractionSequence::Step>
  28. Tutorial::StepBuilder::BuildFromDescriptionStep(
  29. const TutorialDescription::Step& step,
  30. absl::optional<std::pair<int, int>> progress,
  31. bool is_last_step,
  32. bool can_be_restarted,
  33. TutorialService* tutorial_service) {
  34. Tutorial::StepBuilder step_builder(step);
  35. step_builder.SetProgress(progress)
  36. .SetIsLastStep(is_last_step)
  37. .SetCanBeRestarted(can_be_restarted);
  38. return step_builder.Build(tutorial_service);
  39. }
  40. Tutorial::StepBuilder& Tutorial::StepBuilder::SetAnchorElementID(
  41. ui::ElementIdentifier anchor_element_id) {
  42. // Element ID and Element Name are mutually exclusive
  43. DCHECK(!anchor_element_id || step_.element_name.empty());
  44. step_.element_id = anchor_element_id;
  45. return *this;
  46. }
  47. Tutorial::StepBuilder& Tutorial::StepBuilder::SetAnchorElementName(
  48. std::string anchor_element_name) {
  49. // Element ID and Element Name are mutually exclusive
  50. DCHECK(anchor_element_name.empty() || !step_.element_id);
  51. step_.element_name = anchor_element_name;
  52. return *this;
  53. }
  54. Tutorial::StepBuilder& Tutorial::StepBuilder::SetTitleTextID(
  55. int title_text_id) {
  56. step_.title_text_id = title_text_id;
  57. return *this;
  58. }
  59. Tutorial::StepBuilder& Tutorial::StepBuilder::SetBodyTextID(int body_text_id) {
  60. step_.body_text_id = body_text_id;
  61. return *this;
  62. }
  63. Tutorial::StepBuilder& Tutorial::StepBuilder::SetStepType(
  64. ui::InteractionSequence::StepType step_type_,
  65. ui::CustomElementEventType event_type_) {
  66. DCHECK_EQ(step_type_ == ui::InteractionSequence::StepType::kCustomEvent,
  67. static_cast<bool>(event_type_))
  68. << "`event_type_` should be set if and only if `step_type_` is "
  69. "kCustomEvent.";
  70. step_.step_type = step_type_;
  71. step_.event_type = event_type_;
  72. return *this;
  73. }
  74. Tutorial::StepBuilder& Tutorial::StepBuilder::SetProgress(
  75. absl::optional<std::pair<int, int>> progress_) {
  76. progress = progress_;
  77. return *this;
  78. }
  79. Tutorial::StepBuilder& Tutorial::StepBuilder::SetArrow(HelpBubbleArrow arrow_) {
  80. step_.arrow = arrow_;
  81. return *this;
  82. }
  83. Tutorial::StepBuilder& Tutorial::StepBuilder::SetIsLastStep(
  84. bool is_last_step_) {
  85. is_last_step = is_last_step_;
  86. return *this;
  87. }
  88. Tutorial::StepBuilder& Tutorial::StepBuilder::SetMustRemainVisible(
  89. bool must_remain_visible_) {
  90. step_.must_remain_visible = must_remain_visible_;
  91. return *this;
  92. }
  93. Tutorial::StepBuilder& Tutorial::StepBuilder::SetTransitionOnlyOnEvent(
  94. bool transition_only_on_event_) {
  95. step_.transition_only_on_event = transition_only_on_event_;
  96. return *this;
  97. }
  98. Tutorial::StepBuilder& Tutorial::StepBuilder::SetNameElementsCallback(
  99. TutorialDescription::NameElementsCallback name_elements_callback_) {
  100. step_.name_elements_callback = name_elements_callback_;
  101. return *this;
  102. }
  103. Tutorial::StepBuilder& Tutorial::StepBuilder::SetCanBeRestarted(
  104. bool can_be_restarted_) {
  105. can_be_restarted = can_be_restarted_;
  106. return *this;
  107. }
  108. std::unique_ptr<ui::InteractionSequence::Step> Tutorial::StepBuilder::Build(
  109. TutorialService* tutorial_service) {
  110. std::unique_ptr<ui::InteractionSequence::StepBuilder>
  111. interaction_sequence_step_builder =
  112. std::make_unique<ui::InteractionSequence::StepBuilder>();
  113. if (step_.element_id)
  114. interaction_sequence_step_builder->SetElementID(step_.element_id);
  115. if (!step_.element_name.empty())
  116. interaction_sequence_step_builder->SetElementName(step_.element_name);
  117. interaction_sequence_step_builder->SetType(step_.step_type, step_.event_type);
  118. if (step_.must_remain_visible.has_value())
  119. interaction_sequence_step_builder->SetMustRemainVisible(
  120. step_.must_remain_visible.value());
  121. interaction_sequence_step_builder->SetTransitionOnlyOnEvent(
  122. step_.transition_only_on_event);
  123. interaction_sequence_step_builder->SetStartCallback(
  124. BuildStartCallback(tutorial_service));
  125. interaction_sequence_step_builder->SetEndCallback(
  126. BuildHideBubbleCallback(tutorial_service));
  127. return interaction_sequence_step_builder->Build();
  128. }
  129. ui::InteractionSequence::StepStartCallback
  130. Tutorial::StepBuilder::BuildStartCallback(TutorialService* tutorial_service) {
  131. // get show bubble callback
  132. ui::InteractionSequence::StepStartCallback maybe_show_bubble_callback =
  133. BuildMaybeShowBubbleCallback(tutorial_service);
  134. return base::BindOnce(
  135. [](TutorialDescription::NameElementsCallback name_elements_callback,
  136. ui::InteractionSequence::StepStartCallback maybe_show_bubble_callback,
  137. ui::InteractionSequence* sequence, ui::TrackedElement* element) {
  138. if (name_elements_callback)
  139. name_elements_callback.Run(sequence, element);
  140. if (maybe_show_bubble_callback)
  141. std::move(maybe_show_bubble_callback).Run(sequence, element);
  142. },
  143. step_.name_elements_callback, std::move(maybe_show_bubble_callback));
  144. }
  145. ui::InteractionSequence::StepStartCallback
  146. Tutorial::StepBuilder::BuildMaybeShowBubbleCallback(
  147. TutorialService* tutorial_service) {
  148. if (!step_.ShouldShowBubble())
  149. return ui::InteractionSequence::StepStartCallback();
  150. const std::u16string title_text =
  151. step_.title_text_id ? l10n_util::GetStringUTF16(step_.title_text_id)
  152. : std::u16string();
  153. const std::u16string body_text =
  154. step_.body_text_id ? l10n_util::GetStringUTF16(step_.body_text_id)
  155. : std::u16string();
  156. return base::BindOnce(
  157. [](TutorialService* tutorial_service, std::u16string title_text_,
  158. std::u16string body_text_, HelpBubbleArrow arrow_,
  159. absl::optional<std::pair<int, int>> progress_, bool is_last_step_,
  160. bool can_be_restarted_, ui::InteractionSequence* sequence,
  161. ui::TrackedElement* element) {
  162. DCHECK(tutorial_service);
  163. tutorial_service->HideCurrentBubbleIfShowing();
  164. HelpBubbleParams params;
  165. params.title_text = title_text_;
  166. params.body_text = body_text_;
  167. params.progress = progress_;
  168. params.arrow = arrow_;
  169. params.timeout = base::TimeDelta();
  170. params.dismiss_callback = base::BindOnce(
  171. [](absl::optional<int> step_number,
  172. TutorialService* tutorial_service) {
  173. tutorial_service->AbortTutorial(step_number);
  174. },
  175. progress_.has_value() ? absl::make_optional(progress_.value().first)
  176. : absl::nullopt,
  177. base::Unretained(tutorial_service));
  178. if (is_last_step_) {
  179. params.body_icon = &vector_icons::kCelebrationIcon;
  180. params.body_icon_alt_text =
  181. tutorial_service->GetBodyIconAltText(true);
  182. params.dismiss_callback = base::BindOnce(
  183. [](TutorialService* tutorial_service) {
  184. tutorial_service->CompleteTutorial();
  185. },
  186. base::Unretained(tutorial_service));
  187. if (can_be_restarted_) {
  188. HelpBubbleButtonParams restart_button;
  189. restart_button.text =
  190. l10n_util::GetStringUTF16(IDS_TUTORIAL_RESTART_TUTORIAL);
  191. restart_button.is_default = false;
  192. restart_button.callback = base::BindOnce(
  193. [](TutorialService* tutorial_service) {
  194. tutorial_service->RestartTutorial();
  195. },
  196. base::Unretained(tutorial_service));
  197. params.buttons.emplace_back(std::move(restart_button));
  198. }
  199. HelpBubbleButtonParams close_button;
  200. close_button.text =
  201. l10n_util::GetStringUTF16(IDS_TUTORIAL_CLOSE_TUTORIAL);
  202. close_button.is_default = true;
  203. close_button.callback = base::BindOnce(
  204. [](TutorialService* tutorial_service) {
  205. tutorial_service->CompleteTutorial();
  206. },
  207. base::Unretained(tutorial_service));
  208. params.buttons.emplace_back(std::move(close_button));
  209. }
  210. params.close_button_alt_text =
  211. l10n_util::GetStringUTF16(IDS_CLOSE_TUTORIAL);
  212. std::unique_ptr<HelpBubble> bubble =
  213. tutorial_service->bubble_factory_registry()->CreateHelpBubble(
  214. element, std::move(params));
  215. tutorial_service->SetCurrentBubble(std::move(bubble));
  216. },
  217. base::Unretained(tutorial_service), title_text, body_text, step_.arrow,
  218. progress, is_last_step, can_be_restarted);
  219. }
  220. ui::InteractionSequence::StepEndCallback
  221. Tutorial::StepBuilder::BuildHideBubbleCallback(
  222. TutorialService* tutorial_service) {
  223. return base::BindOnce(
  224. [](TutorialService* tutorial_service, ui::TrackedElement* element) {},
  225. base::Unretained(tutorial_service));
  226. }
  227. Tutorial::Builder::Builder()
  228. : builder_(std::make_unique<ui::InteractionSequence::Builder>()) {}
  229. Tutorial::Builder::~Builder() = default;
  230. // static
  231. std::unique_ptr<Tutorial> Tutorial::Builder::BuildFromDescription(
  232. const TutorialDescription& description,
  233. TutorialService* tutorial_service,
  234. ui::ElementContext context) {
  235. Tutorial::Builder builder;
  236. builder.SetContext(context);
  237. // Last step doesn't have a progress counter.
  238. const int max_progress =
  239. std::count_if(description.steps.begin(), description.steps.end(),
  240. [](const auto& step) { return step.ShouldShowBubble(); }) -
  241. 1;
  242. int current_step = 0;
  243. for (const auto& step : description.steps) {
  244. const bool is_last_step = &step == &description.steps.back();
  245. if (!is_last_step && step.ShouldShowBubble())
  246. ++current_step;
  247. const auto progress =
  248. !is_last_step && max_progress > 0
  249. ? absl::make_optional(std::make_pair(current_step, max_progress))
  250. : absl::nullopt;
  251. builder.AddStep(Tutorial::StepBuilder::BuildFromDescriptionStep(
  252. step, progress, is_last_step, description.can_be_restarted,
  253. tutorial_service));
  254. }
  255. DCHECK_EQ(current_step, max_progress);
  256. // Note that the step number we are using here is not the same as the the
  257. // InteractionSequence::AbortCallback step (`sequence_step`) which counts all
  258. // steps; `current_step` in this case is the visual bubble count, which does
  259. // not count hidden steps.
  260. builder.SetAbortedCallback(base::BindOnce(
  261. [](int step_number, TutorialService* tutorial_service, int sequence_step,
  262. ui::TrackedElement* last_element, ui::ElementIdentifier last_id,
  263. ui::InteractionSequence::StepType last_step_type,
  264. ui::InteractionSequence::AbortedReason aborted_reason) {
  265. tutorial_service->AbortTutorial(step_number);
  266. },
  267. current_step, tutorial_service));
  268. return builder.Build();
  269. }
  270. Tutorial::Builder& Tutorial::Builder::AddStep(
  271. std::unique_ptr<ui::InteractionSequence::Step> step) {
  272. builder_->AddStep(std::move(step));
  273. return *this;
  274. }
  275. Tutorial::Builder& Tutorial::Builder::SetAbortedCallback(
  276. ui::InteractionSequence::AbortedCallback callback) {
  277. builder_->SetAbortedCallback(std::move(callback));
  278. return *this;
  279. }
  280. Tutorial::Builder& Tutorial::Builder::SetCompletedCallback(
  281. ui::InteractionSequence::CompletedCallback callback) {
  282. builder_->SetCompletedCallback(std::move(callback));
  283. return *this;
  284. }
  285. Tutorial::Builder& Tutorial::Builder::SetContext(
  286. ui::ElementContext element_context) {
  287. builder_->SetContext(element_context);
  288. return *this;
  289. }
  290. std::unique_ptr<Tutorial> Tutorial::Builder::Build() {
  291. return absl::WrapUnique(new Tutorial(builder_->Build()));
  292. }
  293. Tutorial::Tutorial(
  294. std::unique_ptr<ui::InteractionSequence> interaction_sequence)
  295. : interaction_sequence_(std::move(interaction_sequence)) {}
  296. Tutorial::~Tutorial() = default;
  297. void Tutorial::Start() {
  298. DCHECK(interaction_sequence_);
  299. if (interaction_sequence_)
  300. interaction_sequence_->Start();
  301. }
  302. void Tutorial::Abort() {
  303. if (interaction_sequence_)
  304. interaction_sequence_.reset();
  305. }
  306. } // namespace user_education