#include "ash/system/focus_mode/focus_mode_tasks_model.h"
#include <absl/cleanup/cleanup.h>
#include <algorithm>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/observer_list.h"
namespace ash {
namespace {
FocusModeTask* FindTaskById(const TaskId& task_id,
std::vector<FocusModeTask>& tasks) {
auto iter = std::ranges::find(tasks, task_id, &FocusModeTask::task_id);
return (iter == tasks.end()) ? nullptr : &(*iter);
}
void NotifyCompletedTask(
base::ObserverList<FocusModeTasksModel::Observer>& observers,
const FocusModeTask& task) {
for (FocusModeTasksModel::Observer& observer : observers) {
observer.OnTaskCompleted(task);
}
}
void NotifySelectedTask(
base::ObserverList<FocusModeTasksModel::Observer>& observers,
const FocusModeTask* selected_task) {
const std::optional<FocusModeTask> task =
selected_task ? std::make_optional(*selected_task) : std::nullopt;
for (FocusModeTasksModel::Observer& observer : observers) {
observer.OnSelectedTaskChanged(task);
}
}
void NotifyTaskListChanged(
base::ObserverList<FocusModeTasksModel::Observer>& observers,
const std::vector<FocusModeTask>& tasks) {
for (FocusModeTasksModel::Observer& observer : observers) {
observer.OnTasksUpdated(tasks);
}
}
}
FocusModeTasksModel::TaskUpdate::TaskUpdate() = default;
FocusModeTasksModel::TaskUpdate::TaskUpdate(const TaskUpdate&) = default;
FocusModeTasksModel::TaskUpdate::~TaskUpdate() = default;
FocusModeTasksModel::TaskUpdate
FocusModeTasksModel::TaskUpdate::CompletedUpdate(const TaskId& task_id) {
TaskUpdate update;
update.task_id = task_id;
update.completed = true;
return update;
}
FocusModeTasksModel::TaskUpdate FocusModeTasksModel::TaskUpdate::TitleUpdate(
const TaskId& task_id,
std::string_view title) {
TaskUpdate update;
update.task_id = task_id;
update.title = std::string{title};
return update;
}
FocusModeTasksModel::TaskUpdate FocusModeTasksModel::TaskUpdate::NewTask(
std::string_view title) {
TaskUpdate update;
update.title = std::string{title};
return update;
}
FocusModeTasksModel::FocusModeTasksModel() = default;
FocusModeTasksModel::~FocusModeTasksModel() = default;
void FocusModeTasksModel::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void FocusModeTasksModel::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void FocusModeTasksModel::SetDelegate(base::WeakPtr<Delegate> delegate) {
delegate_ = std::move(delegate);
}
void FocusModeTasksModel::RequestUpdate() {
if (!tasks_.empty()) {
NotifyTaskListChanged(observers_, tasks_);
}
if (selected_task_) {
NotifySelectedTask(observers_, selected_task_);
}
if (delegate_) {
if (selected_task_ && selected_task_->task_id.IsValid()) {
delegate_->FetchTask(
selected_task_->task_id,
base::BindOnce(&FocusModeTasksModel::OnSelectedTaskFetched,
weak_ptr_factory_.GetWeakPtr()));
return;
}
delegate_->FetchTasks();
}
}
bool FocusModeTasksModel::SetSelectedTask(const TaskId& task_id) {
CHECK(!task_id.empty());
auto iter = std::ranges::find(tasks_, task_id, &FocusModeTask::task_id);
if (iter == tasks_.end()) {
return false;
}
pref_task_id_.reset();
if (selected_task_ && selected_task_->task_id == task_id) {
return true;
}
if (iter != tasks_.begin()) {
const std::optional<TaskId> pending_task_id =
pending_task_ ? std::make_optional(pending_task_->task_id)
: std::nullopt;
pending_task_ = nullptr;
selected_task_ = nullptr;
const auto desired = iter;
iter++;
std::rotate(tasks_.begin(), desired, iter);
if (pending_task_id) {
pending_task_ = FindTaskById(*pending_task_id, tasks_);
}
}
selected_task_ = &tasks_[0];
NotifySelectedTask(observers_, selected_task_);
return true;
}
void FocusModeTasksModel::SetSelectedTask(const FocusModeTask& task) {
CHECK(!task.task_id.empty());
FocusModeTask* task_in_list = FindTaskById(task.task_id, tasks_);
if (task_in_list) {
*task_in_list = task;
} else {
InsertTaskIntoTaskList(FocusModeTask(task));
NotifyTaskListChanged(observers_, tasks_);
}
CHECK(SetSelectedTask(task.task_id));
}
void FocusModeTasksModel::ClearSelectedTask() {
pref_task_id_.reset();
if (selected_task_) {
selected_task_ = nullptr;
NotifySelectedTask(observers_, nullptr);
}
if (pending_task_) {
auto iter = std::ranges::find(tasks_, pending_task_->task_id,
&FocusModeTask::task_id);
CHECK(iter != tasks_.end());
pending_task_ = nullptr;
tasks_.erase(iter);
NotifyTaskListChanged(observers_, tasks_);
}
}
void FocusModeTasksModel::Reset() {
NotifySelectedTask(observers_, nullptr);
NotifyTaskListChanged(observers_, {});
selected_task_ = nullptr;
pending_task_ = nullptr;
pref_task_id_.reset();
tasks_.clear();
}
void FocusModeTasksModel::SetSelectedTaskFromPrefs(const TaskId& task_id) {
if (selected_task_) {
return;
}
FocusModeTask* matching_task = FindTaskById(task_id, tasks_);
if (matching_task) {
selected_task_ = matching_task;
NotifySelectedTask(observers_, selected_task_);
return;
}
pref_task_id_ = task_id;
FocusModeTasksModel::Delegate::FetchTaskCallback callback = base::BindOnce(
&FocusModeTasksModel::OnPrefTaskFetched, weak_ptr_factory_.GetWeakPtr());
if (delegate_ && pref_task_id_->IsValid()) {
delegate_->FetchTask(*pref_task_id_, std::move(callback));
}
}
void FocusModeTasksModel::SetTaskList(std::vector<FocusModeTask>&& tasks) {
TaskId desired_task_id;
if (selected_task_) {
desired_task_id = selected_task_->task_id;
} else if (pref_task_id_) {
desired_task_id = *pref_task_id_;
}
bool desired_task_in_list = false;
if (!desired_task_id.empty()) {
FocusModeTask* new_selected_task = FindTaskById(desired_task_id, tasks);
if (new_selected_task) {
desired_task_in_list = true;
} else if (selected_task_) {
tasks.insert(tasks.begin(), *selected_task_);
desired_task_in_list = true;
}
}
pending_task_ = nullptr;
selected_task_ = nullptr;
tasks_ = tasks;
if (desired_task_in_list) {
selected_task_ = FindTaskById(desired_task_id, tasks_);
pref_task_id_.reset();
}
NotifyTaskListChanged(observers_, tasks_);
if (desired_task_id.empty()) {
return;
}
if (pref_task_id_) {
return;
}
NotifySelectedTask(observers_, selected_task_);
}
void FocusModeTasksModel::UpdateTask(const TaskUpdate& task_update) {
FocusModeTask* task = nullptr;
if (task_update.task_id) {
task = FindTaskById(*task_update.task_id, tasks_);
} else {
FocusModeTask new_task;
new_task.task_id.pending = true;
selected_task_ = nullptr;
pending_task_ = nullptr;
task = &(*tasks_.insert(tasks_.begin(), std::move(new_task)));
pending_task_ = task;
selected_task_ = pending_task_;
}
if (task == nullptr) {
return;
}
if (task_update.title) {
task->title = *task_update.title;
}
if (task_update.completed.has_value()) {
CHECK(task_update.task_id);
const TaskId& id = *task_update.task_id;
auto iter = std::ranges::find(tasks_, id, &FocusModeTask::task_id);
CHECK(iter != tasks_.end());
NotifyCompletedTask(observers_, *iter);
if (selected_task_ == task) {
if (pending_task_ == selected_task_) {
pending_task_ = nullptr;
}
selected_task_ = nullptr;
}
tasks_.erase(iter);
}
if (delegate_) {
if (!task_update.task_id) {
delegate_->AddTask(task_update,
base::BindOnce(&FocusModeTasksModel::OnTaskAdded,
weak_ptr_factory_.GetWeakPtr()));
} else {
if (!task_update.task_id->pending) {
delegate_->UpdateTask(task_update);
}
}
}
NotifyTaskListChanged(observers_, tasks_);
NotifySelectedTask(observers_, selected_task_);
}
const std::vector<FocusModeTask>& FocusModeTasksModel::tasks() const {
return tasks_;
}
const FocusModeTask* FocusModeTasksModel::selected_task() const {
return selected_task_;
}
const FocusModeTask* FocusModeTasksModel::PendingTaskForTesting() const {
return pending_task_;
}
const TaskId& FocusModeTasksModel::PrefTaskIdForTesting() const {
return *pref_task_id_;
}
void FocusModeTasksModel::OnTaskAdded(
const std::optional<FocusModeTask>& fetched_task) {
if (!pending_task_) {
LOG(WARNING) << "Update for a task that is no longer pending";
return;
}
if (!pending_task_->task_id.pending) {
LOG(WARNING) << "Pending task already has an id";
return;
}
if (!fetched_task) {
LOG(WARNING) << "Adding task failed";
return;
}
*pending_task_ = *fetched_task;
if (pending_task_ == selected_task_) {
NotifySelectedTask(observers_, selected_task_);
}
pending_task_ = nullptr;
NotifyTaskListChanged(observers_, tasks_);
}
void FocusModeTasksModel::OnPrefTaskFetched(
const std::optional<FocusModeTask>& fetched_task) {
if (!fetched_task) {
LOG(WARNING) << "Fetching Pref task failed. Try again later";
return;
}
if (selected_task_ || !pref_task_id_) {
pref_task_id_.reset();
return;
}
const TaskId& task_id = fetched_task->task_id;
if (task_id != *pref_task_id_) {
return;
}
bool list_updated = true;
auto iter = std::ranges::find(tasks_, task_id, &FocusModeTask::task_id);
if (iter != tasks_.end()) {
if (fetched_task->completed) {
tasks_.erase(iter);
} else {
list_updated = false;
selected_task_ = &(*iter);
}
} else {
if (!fetched_task->completed) {
selected_task_ = InsertTaskIntoTaskList(FocusModeTask(*fetched_task));
}
}
pref_task_id_.reset();
if (list_updated) {
NotifyTaskListChanged(observers_, tasks_);
}
NotifySelectedTask(observers_, selected_task_);
}
void FocusModeTasksModel::OnSelectedTaskFetched(
const std::optional<FocusModeTask>& fetched_task) {
CHECK(delegate_);
if (!fetched_task) {
LOG(WARNING) << "Fetching Selected task failed. Try again later";
return;
}
absl::Cleanup fetch_tasks = [this] { delegate_->FetchTasks(); };
const TaskId& task_id = fetched_task->task_id;
FocusModeTask* task = FindTaskById(task_id, tasks_);
if (task == nullptr) {
return;
}
bool has_task_title_changed = task->title != fetched_task->title;
if (has_task_title_changed) {
task->title = fetched_task->title;
}
if (fetched_task->completed) {
UpdateTask(TaskUpdate::CompletedUpdate(task_id));
return;
}
if (has_task_title_changed && selected_task_ &&
selected_task_->task_id == task_id) {
NotifySelectedTask(observers_, selected_task_);
}
}
FocusModeTask* FocusModeTasksModel::InsertTaskIntoTaskList(
FocusModeTask&& task) {
std::optional<TaskId> selected_task_id =
selected_task_ ? std::make_optional(selected_task_->task_id)
: std::nullopt;
std::optional<TaskId> pending_task_id =
pending_task_ ? std::make_optional(pending_task_->task_id) : std::nullopt;
pending_task_ = nullptr;
selected_task_ = nullptr;
auto inserted = tasks_.insert(tasks_.begin(), task);
if (selected_task_id) {
selected_task_ = FindTaskById(*selected_task_id, tasks_);
}
if (pending_task_id) {
if (pending_task_id == selected_task_id) {
pending_task_ = selected_task_;
} else {
pending_task_ = FindTaskById(*pending_task_id, tasks_);
}
}
return &(*inserted);
}
}