chromium/ash/glanceables/tasks/glanceables_tasks_view.h

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef ASH_GLANCEABLES_TASKS_GLANCEABLES_TASKS_VIEW_H_
#define ASH_GLANCEABLES_TASKS_GLANCEABLES_TASKS_VIEW_H_

#include <memory>
#include <optional>

#include "ash/api/tasks/tasks_client.h"
#include "ash/api/tasks/tasks_types.h"
#include "ash/ash_export.h"
#include "ash/glanceables/common/glanceables_time_management_bubble_view.h"
#include "ash/glanceables/glanceables_metrics.h"
#include "ash/glanceables/tasks/glanceables_tasks_error_type.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "google_apis/common/api_error_codes.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/models/list_model.h"
#include "ui/gfx/animation/animation_delegate.h"
#include "ui/views/view.h"
#include "ui/views/view_model.h"

class GURL;

namespace views {
class LabelButton;
}  // namespace views

namespace ash {

class GlanceablesTasksComboboxModel;
class GlanceablesTaskView;

// Glanceables view responsible for interacting with Google Tasks.
class ASH_EXPORT GlanceablesTasksView
    : public GlanceablesTimeManagementBubbleView {
  METADATA_HEADER(GlanceablesTasksView, GlanceablesTimeManagementBubbleView)
 public:
  explicit GlanceablesTasksView(const ui::ListModel<api::TaskList>* task_lists);
  GlanceablesTasksView(const GlanceablesTasksView&) = delete;
  GlanceablesTasksView& operator=(const GlanceablesTasksView&) = delete;
  ~GlanceablesTasksView() override;

  // views::View:
  void Layout(PassKey) override;
  gfx::Size CalculatePreferredSize(
      const views::SizeBounds& available_size) const override;

  // GlanceablesTimeManagementBubbleView:
  void AnimationEnded(const gfx::Animation* animation) override;

  // Invalidates any pending tasks, or tasks lists requests. Called when the
  // glanceables bubble widget starts closing to avoid unnecessary UI updates.
  void CancelUpdates();

  // Updates the cached task lists to `task_lists` and the tasks that are
  // supposed to show.
  void UpdateTaskLists(const ui::ListModel<api::TaskList>* task_lists);

  void EndResizeAnimationForTest();

 private:
  // The context of why the current task list is shown.
  enum class ListShownContext {
    // The list is a cached one that will be updated later after the lists data
    // are fetched.
    kCachedList,
    // The list that is loaded by default when users open glanceables.
    kInitialList,
    // The list is manually selected by the users through
    // `task_list_combo_box_view_`.
    kUserSelectedList
  };

  // GlanceablesTimeManagementBubbleView:
  void OnHeaderIconPressed() override;
  void OnFooterButtonPressed() override;
  void SelectedListChanged() override;
  void AnimateResize(ResizeAnimation::Type resize_type) override;

  // Handles press behavior for `add_new_task_button_`.
  void AddNewTaskButtonPressed();

  // Creates a `GlanceablesTaskView` instance with bound callbacks.
  std::unique_ptr<GlanceablesTaskView> CreateTaskView(
      const std::string& task_list_id,
      const api::Task* task);

  // Handles switching between tasks lists.
  void ScheduleUpdateTasks(ListShownContext context);
  void RetryUpdateTasks(ListShownContext context);
  void UpdateTasksInTaskList(
      const std::string& task_list_id,
      const std::string& task_list_title,
      ListShownContext context,
      bool fetch_success,
      std::optional<google_apis::ApiErrorCode> http_error,
      const ui::ListModel<api::Task>* tasks);

  // Called as a `state_change_callback` when a task view state changes between
  // "view" and "edit" state, which causes changes in the task view preferred
  // size. `view_expanding` indicates whether the task view is expanding or
  // collapsing in size.
  void HandleTaskViewStateChange(bool view_expanding);

  // Marks the specified task as completed.
  void MarkTaskAsCompleted(const std::string& task_list_id,
                           const std::string& task_id,
                           bool completed);

  // Handles press behavior for icons that are used to open Google Tasks in the
  // browser.
  void ActionButtonPressed(TasksLaunchSource source, const GURL& target_url);

  // Saves the task (either creates or updates the existing one).
  // `view`     - individual task view which triggered this request.
  // `callback` - done callback passed from an individual task view.
  void SaveTask(const std::string& task_list_id,
                base::WeakPtr<GlanceablesTaskView> view,
                const std::string& task_id,
                const std::string& title,
                api::TasksClient::OnTaskSavedCallback callback);

  // Handles completion of `SaveTask`.
  // `view`     - individual task view which triggered this request.
  // `callback` - callback passed from an individual task view via `SaveTask`.
  // `task`     - newly created or edited task if the request completes
  //              successfully, `nullptr` otherwise.
  void OnTaskSaved(base::WeakPtr<GlanceablesTaskView> view,
                   const std::string& task_id,
                   api::TasksClient::OnTaskSavedCallback callback,
                   google_apis::ApiErrorCode http_error,
                   const api::Task* task);

  // Returns the current showing task list.
  const api::TaskList* GetActiveTaskList() const;

  // Creates and shows `error_message_` that depends on the `error_type` and
  // `button_action_type`.
  void ShowErrorMessageWithType(
      GlanceablesTasksErrorType error_type,
      ErrorMessageToast::ButtonActionType button_action_type);

  // Returns the string to show on `error_message_` according to the
  // `error_type`.
  std::u16string GetErrorString(GlanceablesTasksErrorType error_type) const;

  // Removes `task_view` from the tasks container.
  void RemoveTaskView(base::WeakPtr<GlanceablesTaskView> task_view);

  // This function should be called with `is_loading` = true if `this` is
  // waiting for fetched data to be returned. After the data arrives, resets the
  // states by calling with `is_loading` = false.
  void SetIsLoading(bool is_loading);

  // Animates visibility updates for a task view. It assumes that at most one
  // task view changes visibility at the time - currently, this is exclusively
  // used for task view added to the list in response to the user clicking the
  // button to add a new task.
  // When hiding/removing the task view, a copy of the task view layer will
  // be created for the animation, so the view can be hidden/removed
  // immediately.
  void AnimateTaskViewVisibility(views::View* task, bool visible);
  void OnTaskViewAnimationCompleted();

  // Caching `combobox_model_` from GlanceablesTimeManagementBubbleView.
  raw_ptr<GlanceablesTasksComboboxModel> tasks_combobox_model_;

  // The number of times that the tasks list has been changed during the
  // lifetime of this view.
  int tasks_list_change_count_ = 0;

  // Whether the first task list has been shown during the lifetime of this
  // view.
  bool first_task_list_shown_ = false;

  // Owned by views hierarchy.
  raw_ptr<views::LabelButton> add_new_task_button_ = nullptr;

  // An invisible view added at the last element to the task list container to
  // more easily track the offset of the bottom of the list from the target
  // position mid task view state change animations. The transform will be used
  // to synchronize `resize_animation_` with task view resize / task list layout
  // animations - if the sentinel, i.e. the bottom of the task list is
  // animating, the bubble preferred size will be "offset" by the sentinel
  // transform so the bottom of the bubble tracks the bottom of the task list
  // (otherwise, if the animations get slightly out of sync, animating task
  // views may appear to jitter as their bottom padding changes slightly).
  raw_ptr<views::View> task_list_sentinel_ = nullptr;

  // Copy of a task view whose visibility is animating to "hidden". The copy
  // of the layer is used so the original view can be removed from the view
  // hierarchy immediately.
  std::unique_ptr<ui::Layer> animating_task_view_layer_;

  // The type of resize animation that is currently running.
  std::optional<ResizeAnimation::Type> running_resize_animation_ = std::nullopt;

  // Records the time when the bubble was about to request a task list. Used for
  // metrics.
  base::TimeTicks tasks_requested_time_;

  // Cached to reset the value of the index of `task_list_combo_box_view_` when
  // the target task list failed to be loaded.
  std::optional<size_t> cached_selected_list_index_ = std::nullopt;

  // Number of tasks added by the user for the currently selected task list.
  // Task is considered "added" if task creation was requested via tasks API.
  // The count is reset when the selected task list changes.
  int added_tasks_ = 0;

  // Whether the current task list was empty when it got selected.
  bool task_list_initially_empty_ = false;

  // Whether the user had a single task list with no tasks when the current task
  // list was selected.
  bool user_with_no_tasks_ = false;

  // Time stamp of when the view was created.
  const base::Time shown_time_;

  // The model containing task views shown within the tasks bubble. Used to set
  // up task view animations in response to a task view change. When animating
  // task views, their transform is calculated relative to expected view bounds
  // after an imminent layout. The reference/target bounds for the animation
  // are cached as view ideal bounds in the model, so the transform can be
  // recalculated if another task view state changes before the task list view
  // layout gets updated (e.g. if the same user action causes once view to
  // transition to kEdit, and another one to kView state).
  views::ViewModelT<views::View> task_view_model_;

  // Callback that recreates `task_list_combo_box_view_`.
  base::OnceClosure recreate_combobox_callback_;

  base::WeakPtrFactory<GlanceablesTasksView> weak_ptr_factory_{this};
};

}  // namespace ash

#endif  // ASH_GLANCEABLES_TASKS_GLANCEABLES_TASKS_VIEW_H_