/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_todo_list.h" #include "api/api_text_entities.h" #include "data/data_user.h" #include "data/data_session.h" #include "base/call_delayed.h" #include "history/history_item.h" #include "main/main_session.h" #include "api/api_text_entities.h" #include "ui/text/text_options.h" namespace { constexpr auto kShortPollTimeout = 30 * crl::time(1000); const TodoListItem *ItemById(const std::vector &list, int id) { const auto i = ranges::find(list, id, &TodoListItem::id); return (i != end(list)) ? &*i : nullptr; } TodoListItem *ItemById(std::vector &list, int id) { return const_cast(ItemById(std::as_const(list), id)); } } // namespace TodoListData::TodoListData(not_null owner, TodoListId id) : id(id) , _owner(owner) { } Data::Session &TodoListData::owner() const { return *_owner; } Main::Session &TodoListData::session() const { return _owner->session(); } bool TodoListData::applyChanges(const MTPDtodoList &todolist) { const auto newTitle = TextWithEntities{ .text = qs(todolist.vtitle().data().vtext()), .entities = Api::EntitiesFromMTP( &session(), todolist.vtitle().data().ventities().v), }; const auto newFlags = (todolist.is_others_can_append() ? Flag::OthersCanAppend : Flag()) | (todolist.is_others_can_complete() ? Flag::OthersCanComplete : Flag()); auto newItems = ranges::views::all( todolist.vlist().v ) | ranges::views::transform([&](const MTPTodoItem &item) { return TodoListItemFromMTP(&session(), item); }) | ranges::views::take( kMaxOptions ) | ranges::to_vector; const auto changed1 = (title != newTitle) || (_flags != newFlags); const auto changed2 = (items != newItems); if (!changed1 && !changed2) { return false; } if (changed1) { title = newTitle; _flags = newFlags; } if (changed2) { std::swap(items, newItems); for (const auto &old : newItems) { if (const auto current = itemById(old.id)) { current->completedBy = old.completedBy; current->completionDate = old.completionDate; } } } ++version; return true; } bool TodoListData::applyCompletions( const MTPVector *completions) { auto changed = false; const auto lookup = [&](int id) { if (!completions) { return (const MTPDtodoCompletion*)nullptr; } const auto proj = [](const MTPTodoCompletion &completion) { return completion.data().vid().v; }; const auto i = ranges::find(completions->v, id, proj); return (i != completions->v.end()) ? &i->data() : nullptr; }; for (auto &item : items) { const auto completion = lookup(item.id); const auto by = (completion && completion->vcompleted_by().v) ? owner().user(UserId(completion->vcompleted_by().v)).get() : nullptr; const auto date = completion ? completion->vdate().v : TimeId(); if (item.completedBy != by || item.completionDate != date) { item.completedBy = by; item.completionDate = date; changed = true; } } if (changed) { ++version; } return changed; } void TodoListData::apply( not_null item, const MTPDmessageActionTodoCompletions &data) { for (const auto &id : data.vcompleted().v) { if (const auto task = itemById(id.v)) { task->completedBy = item->from(); task->completionDate = item->date(); } } for (const auto &id : data.vincompleted().v) { if (const auto task = itemById(id.v)) { task->completedBy = nullptr; task->completionDate = TimeId(); } } owner().notifyTodoListUpdateDelayed(this); } void TodoListData::apply(const MTPDmessageActionTodoAppendTasks &data) { const auto limit = TodoListData::kMaxOptions; for (const auto &task : data.vlist().v) { if (items.size() < limit) { const auto parsed = TodoListItemFromMTP( &session(), task); if (!itemById(parsed.id)) { items.push_back(std::move(parsed)); } } } owner().notifyTodoListUpdateDelayed(this); } TodoListItem *TodoListData::itemById(int id) { return ItemById(items, id); } const TodoListItem *TodoListData::itemById(int id) const { return ItemById(items, id); } void TodoListData::setFlags(Flags flags) { if (_flags != flags) { _flags = flags; ++version; } } TodoListData::Flags TodoListData::flags() const { return _flags; } bool TodoListData::othersCanAppend() const { return (_flags & Flag::OthersCanAppend); } bool TodoListData::othersCanComplete() const { return (_flags & Flag::OthersCanComplete); } MTPTodoList TodoListDataToMTP(not_null todolist) { const auto convert = [&](const TodoListItem &item) { return MTP_todoItem( MTP_int(item.id), MTP_textWithEntities( MTP_string(item.text.text), Api::EntitiesToMTP( &todolist->session(), item.text.entities))); }; auto items = QVector(); items.reserve(todolist->items.size()); ranges::transform( todolist->items, ranges::back_inserter(items), convert); using Flag = MTPDtodoList::Flag; const auto flags = Flag() | (todolist->othersCanAppend() ? Flag::f_others_can_append : Flag()) | (todolist->othersCanComplete() ? Flag::f_others_can_complete : Flag()); return MTP_todoList( MTP_flags(flags), MTP_textWithEntities( MTP_string(todolist->title.text), Api::EntitiesToMTP( &todolist->session(), todolist->title.entities)), MTP_vector(items)); } MTPInputMedia TodoListDataToInputMedia( not_null todolist) { return MTP_inputMediaTodo(TodoListDataToMTP(todolist)); } TodoListItem TodoListItemFromMTP( not_null session, const MTPTodoItem &item) { const auto &data = item.data(); return { .text = TextWithEntities{ .text = qs(data.vtitle().data().vtext()), .entities = Api::EntitiesFromMTP( session, data.vtitle().data().ventities().v), }, .id = data.vid().v, }; }