Allow fully editing todo lists.

This commit is contained in:
John Preston 2025-06-12 18:41:01 +04:00
parent d83a80ec53
commit 9290c90bdc
17 changed files with 204 additions and 42 deletions

View file

@ -273,8 +273,6 @@ PRIVATE
boxes/connection_box.h boxes/connection_box.h
boxes/create_poll_box.cpp boxes/create_poll_box.cpp
boxes/create_poll_box.h boxes/create_poll_box.h
boxes/create_todo_list_box.cpp
boxes/create_todo_list_box.h
boxes/delete_messages_box.cpp boxes/delete_messages_box.cpp
boxes/delete_messages_box.h boxes/delete_messages_box.h
boxes/dictionaries_manager.cpp boxes/dictionaries_manager.cpp
@ -285,6 +283,8 @@ PRIVATE
boxes/edit_caption_box.h boxes/edit_caption_box.h
boxes/edit_privacy_box.cpp boxes/edit_privacy_box.cpp
boxes/edit_privacy_box.h boxes/edit_privacy_box.h
boxes/edit_todo_list_box.cpp
boxes/edit_todo_list_box.h
boxes/gift_credits_box.cpp boxes/gift_credits_box.cpp
boxes/gift_credits_box.h boxes/gift_credits_box.h
boxes/gift_premium_box.cpp boxes/gift_premium_box.cpp

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_histories.h" #include "data/data_histories.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_todo_list.h"
#include "data/data_web_page.h" #include "data/data_web_page.h"
#include "history/view/controls/history_view_compose_media_edit_manager.h" #include "history/view/controls/history_view_compose_media_edit_manager.h"
#include "history/history.h" #include "history/history.h"
@ -358,4 +359,22 @@ mtpRequestId EditTextMessage(
std::nullopt); std::nullopt);
} }
void EditTodoList(
not_null<HistoryItem*> item,
const TodoListData &data,
SendOptions options,
Fn<void(mtpRequestId requestId)> done,
Fn<void(const QString &error, mtpRequestId requestId)> fail) {
const auto callback = [=](Fn<void()> applyUpdates, mtpRequestId id) {
applyUpdates();
done(id);
};
EditMessage(
item,
options,
callback,
fail,
MTP_inputMediaTodo(TodoListDataToMTP(&data)));
}
} // namespace Api } // namespace Api

View file

@ -58,4 +58,11 @@ mtpRequestId EditTextMessage(
Fn<void(const QString &error, mtpRequestId requestId)> fail, Fn<void(const QString &error, mtpRequestId requestId)> fail,
bool spoilered); bool spoilered);
void EditTodoList(
not_null<HistoryItem*> item,
const TodoListData &data,
SendOptions options,
Fn<void(mtpRequestId requestId)> done,
Fn<void(const QString &error, mtpRequestId requestId)> fail);
} // namespace Api } // namespace Api

View file

@ -7,8 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "api/api_todo_lists.h" #include "api/api_todo_lists.h"
//#include "api/api_common.h" #include "api/api_editing.h"
//#include "api/api_updates.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "base/random.h" #include "base/random.h"
#include "data/business/data_shortcut_messages.h" // ShortcutIdToMTP #include "data/business/data_shortcut_messages.h" // ShortcutIdToMTP
@ -134,6 +133,23 @@ void TodoLists::create(
}); });
} }
void TodoLists::edit(
not_null<HistoryItem*> item,
const TodoListData &data,
SendOptions options,
Fn<void()> done,
Fn<void(QString)> fail) {
EditTodoList(item, data, options, [=](mtpRequestId) {
if (const auto onstack = done) {
onstack();
}
}, [=](const QString &error, mtpRequestId) {
if (const auto onstack = fail) {
onstack(error);
}
});
}
void TodoLists::add( void TodoLists::add(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
const std::vector<TodoListItem> &items, const std::vector<TodoListItem> &items,

View file

@ -22,6 +22,7 @@ class Session;
namespace Api { namespace Api {
struct SendAction; struct SendAction;
struct SendOptions;
class TodoLists final { class TodoLists final {
public: public:
@ -32,6 +33,12 @@ public:
SendAction action, SendAction action,
Fn<void()> done, Fn<void()> done,
Fn<void(QString)> fail); Fn<void(QString)> fail);
void edit(
not_null<HistoryItem*> item,
const TodoListData &data,
SendOptions options,
Fn<void()> done,
Fn<void(QString)> fail);
void add( void add(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
const std::vector<TodoListItem> &items, const std::vector<TodoListItem> &items,

View file

@ -5,7 +5,7 @@ the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link: For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "boxes/create_todo_list_box.h" #include "boxes/edit_todo_list_box.h"
#include "base/call_delayed.h" #include "base/call_delayed.h"
#include "base/event_filter.h" #include "base/event_filter.h"
@ -85,7 +85,6 @@ private:
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session, not_null<Main::Session*> session,
int id, int id,
TextWithEntities text,
int position, int position,
bool locked); bool locked);
@ -144,7 +143,7 @@ private:
int id, int id,
TextWithEntities text, TextWithEntities text,
anim::type animated); anim::type animated);
void initTaskField(not_null<Task*> task); void initTaskField(not_null<Task*> task, TextWithEntities text);
void checkLastTask(); void checkLastTask();
void validateState(); void validateState();
void fixAfterErase(); void fixAfterErase();
@ -249,7 +248,6 @@ Tasks::Task::Task(
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session, not_null<Main::Session*> session,
int id, int id,
TextWithEntities text,
int position, int position,
bool locked) bool locked)
: _id(id) : _id(id)
@ -270,11 +268,6 @@ Tasks::Task::Task(
, _limit(session->appConfig().todoListItemTextLimit()) { , _limit(session->appConfig().todoListItemTextLimit()) {
InitField(outer, _field, session); InitField(outer, _field, session);
_field->setMaxLength(_limit + kErrorLimit); _field->setMaxLength(_limit + kErrorLimit);
_field->setTextWithTags({
text.text,
TextUtilities::ConvertEntitiesToTextTags(text.entities)
});
_field->finishAnimating();
_field->show(); _field->show();
if (locked) { if (locked) {
_field->setDisabled(true); _field->setDisabled(true);
@ -487,7 +480,7 @@ Tasks::Tasks(
for (const auto &task : existing) { for (const auto &task : existing) {
addTask(task.id, task.text, anim::type::instant); addTask(task.id, task.text, anim::type::instant);
} }
checkLastTask(); validateState();
} }
bool Tasks::full() const { bool Tasks::full() const {
@ -539,10 +532,13 @@ std::vector<TodoListItem> Tasks::toTodoListItems() const {
result.reserve(_list.size()); result.reserve(_list.size());
auto usedId = 0; auto usedId = 0;
for (const auto &task : _list) { for (const auto &task : _list) {
if (const auto id = task->id()) {
usedId = id;
} else if (task->isGood()) {
++usedId;
}
if (task->isGood()) { if (task->isGood()) {
result.push_back(task->toTodoListItem(++usedId)); result.push_back(task->toTodoListItem(usedId));
} else if (const auto id = task->id()) {
usedId = std::max(usedId, id);
} }
} }
return result; return result;
@ -646,17 +642,28 @@ void Tasks::addTask(
_container, _container,
&_controller->session(), &_controller->session(),
id, id,
std::move(text),
_position + _list.size() + _destroyed.size(), _position + _list.size() + _destroyed.size(),
locked)); locked));
const auto field = _list.back()->field();
if (!locked) { if (!locked) {
initTaskField(_list.back().get()); initTaskField(_list.back().get(), std::move(text));
} else {
InitMessageFieldHandlers(
_controller,
field,
Window::GifPauseReason::Layer,
[](not_null<DocumentData*>) { return true; });
field->setTextWithTags({
text.text,
TextUtilities::ConvertEntitiesToTextTags(text.entities)
});
} }
field->finishAnimating();
_list.back()->show(animated); _list.back()->show(animated);
fixShadows(); fixShadows();
} }
void Tasks::initTaskField(not_null<Task*> task) { void Tasks::initTaskField(not_null<Task*> task, TextWithEntities text) {
const auto field = task->field(); const auto field = task->field();
if (const auto emojiPanel = _emojiPanel) { if (const auto emojiPanel = _emojiPanel) {
const auto emojiToggle = Ui::AddEmojiToggleToField( const auto emojiToggle = Ui::AddEmojiToggleToField(
@ -686,6 +693,10 @@ void Tasks::initTaskField(not_null<Task*> task) {
}, _emojiPanelLifetime); }, _emojiPanelLifetime);
}, emojiToggle->lifetime()); }, emojiToggle->lifetime());
} }
field->setTextWithTags({
text.text,
TextUtilities::ConvertEntitiesToTextTags(text.entities)
});
field->submits( field->submits(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
const auto index = findField(field); const auto index = findField(field);
@ -789,7 +800,7 @@ void Tasks::checkLastTask() {
} // namespace } // namespace
CreateTodoListBox::CreateTodoListBox( EditTodoListBox::EditTodoListBox(
QWidget*, QWidget*,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
rpl::producer<int> starsRequired, rpl::producer<int> starsRequired,
@ -802,25 +813,41 @@ CreateTodoListBox::CreateTodoListBox(
, _titleLimit(controller->session().appConfig().todoListTitleLimit()) { , _titleLimit(controller->session().appConfig().todoListTitleLimit()) {
} }
auto CreateTodoListBox::submitRequests() const -> rpl::producer<Result> { EditTodoListBox::EditTodoListBox(
QWidget*,
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item)
: _controller(controller)
, _sendMenuDetails([] { return SendMenu::Details(); })
, _editingItem(item)
, _titleLimit(controller->session().appConfig().todoListTitleLimit()) {
_controller->session().changes().messageUpdates(
Data::MessageUpdate::Flag::Destroyed
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
if (update.item == item) {
closeBox();
}
}, lifetime());
}
auto EditTodoListBox::submitRequests() const -> rpl::producer<Result> {
return _submitRequests.events(); return _submitRequests.events();
} }
void CreateTodoListBox::setInnerFocus() { void EditTodoListBox::setInnerFocus() {
_setInnerFocus(); _setInnerFocus();
} }
void CreateTodoListBox::submitFailed(const QString &error) { void EditTodoListBox::submitFailed(const QString &error) {
showToast(error); showToast(error);
} }
not_null<Ui::InputField*> CreateTodoListBox::setupTitle( not_null<Ui::InputField*> EditTodoListBox::setupTitle(
not_null<Ui::VerticalLayout*> container) { not_null<Ui::VerticalLayout*> container) {
using namespace Settings; using namespace Settings;
const auto session = &_controller->session(); const auto session = &_controller->session();
const auto isPremium = session->premium(); const auto isPremium = session->premium();
const auto title = container->add( const auto title = container->add(
object_ptr<Ui::InputField>( object_ptr<Ui::InputField>(
container, container,
@ -860,6 +887,15 @@ not_null<Ui::InputField*> CreateTodoListBox::setupTitle(
}, emojiToggle->lifetime()); }, emojiToggle->lifetime());
} }
const auto media = _editingItem ? _editingItem->media() : nullptr;
if (const auto todolist = media ? media->todolist() : nullptr) {
const auto &text = todolist->title;
title->setTextWithTags({
text.text,
TextUtilities::ConvertEntitiesToTextTags(text.entities)
});
}
const auto warning = CreateWarningLabel( const auto warning = CreateWarningLabel(
container, container,
title, title,
@ -885,7 +921,7 @@ not_null<Ui::InputField*> CreateTodoListBox::setupTitle(
return title; return title;
} }
object_ptr<Ui::RpWidget> CreateTodoListBox::setupContent() { object_ptr<Ui::RpWidget> EditTodoListBox::setupContent() {
using namespace Settings; using namespace Settings;
const auto id = FullMsgId{ const auto id = FullMsgId{
@ -906,11 +942,14 @@ object_ptr<Ui::RpWidget> CreateTodoListBox::setupContent() {
tr::lng_todo_create_list(), tr::lng_todo_create_list(),
st::defaultSubsectionTitle), st::defaultSubsectionTitle),
st::createPollFieldTitlePadding); st::createPollFieldTitlePadding);
const auto media = _editingItem ? _editingItem->media() : nullptr;
const auto todolist = media ? media->todolist() : nullptr;
const auto tasks = lifetime().make_state<Tasks>( const auto tasks = lifetime().make_state<Tasks>(
this, this,
container, container,
_controller, _controller,
_emojiPanel ? _emojiPanel.get() : nullptr); _emojiPanel ? _emojiPanel.get() : nullptr,
todolist ? todolist->items : std::vector<TodoListItem>());
auto limit = tasks->addedCount() | rpl::after_next([=](int count) { auto limit = tasks->addedCount() | rpl::after_next([=](int count) {
setCloseByEscape(!count); setCloseByEscape(!count);
setCloseByOutsideClick(!count); setCloseByOutsideClick(!count);
@ -944,14 +983,14 @@ object_ptr<Ui::RpWidget> CreateTodoListBox::setupContent() {
object_ptr<Ui::Checkbox>( object_ptr<Ui::Checkbox>(
container, container,
tr::lng_todo_create_allow_add(tr::now), tr::lng_todo_create_allow_add(tr::now),
true, !todolist || todolist->othersCanAppend(),
st::defaultCheckbox), st::defaultCheckbox),
st::createPollCheckboxMargin); st::createPollCheckboxMargin);
const auto allowMark = container->add( const auto allowMark = container->add(
object_ptr<Ui::Checkbox>( object_ptr<Ui::Checkbox>(
container, container,
tr::lng_todo_create_allow_mark(tr::now), tr::lng_todo_create_allow_mark(tr::now),
true, !todolist || todolist->othersCanComplete(),
st::defaultCheckbox), st::defaultCheckbox),
st::createPollCheckboxMargin); st::createPollCheckboxMargin);
@ -1019,6 +1058,14 @@ object_ptr<Ui::RpWidget> CreateTodoListBox::setupContent() {
showError(tr::lng_todo_choose_tasks); showError(tr::lng_todo_choose_tasks);
tasks->focusFirst(); tasks->focusFirst();
} else if (!*error) { } else if (!*error) {
if (_editingItem) {
sendOptions = {
.scheduled = (_editingItem->isScheduled()
? _editingItem->date()
: TimeId()),
.shortcutId = _editingItem->shortcutId(),
};
}
_submitRequests.fire({ collectResult(), sendOptions }); _submitRequests.fire({ collectResult(), sendOptions });
} }
}; };
@ -1043,9 +1090,13 @@ object_ptr<Ui::RpWidget> CreateTodoListBox::setupContent() {
_sendMenuDetails()); _sendMenuDetails());
}; };
const auto submit = addButton( const auto submit = addButton(
tr::lng_todo_create_button(), (_editingItem
? tr::lng_settings_save()
: tr::lng_todo_create_button()),
[=] { isNormal ? send({}) : schedule(); }); [=] { isNormal ? send({}) : schedule(); });
submit->setText(PaidSendButtonText(_starsRequired.value(), isNormal submit->setText(PaidSendButtonText(_starsRequired.value(), _editingItem
? tr::lng_settings_save()
: isNormal
? tr::lng_todo_create_button() ? tr::lng_todo_create_button()
: tr::lng_schedule_button())); : tr::lng_schedule_button()));
const auto sendMenuDetails = [=] { const auto sendMenuDetails = [=] {
@ -1062,7 +1113,7 @@ object_ptr<Ui::RpWidget> CreateTodoListBox::setupContent() {
return result; return result;
} }
void CreateTodoListBox::prepare() { void EditTodoListBox::prepare() {
setTitle(tr::lng_todo_create_title()); setTitle(tr::lng_todo_create_title());
const auto inner = setInnerWidget(setupContent()); const auto inner = setInnerWidget(setupContent());

View file

@ -30,19 +30,23 @@ namespace SendMenu {
struct Details; struct Details;
} // namespace SendMenu } // namespace SendMenu
class CreateTodoListBox : public Ui::BoxContent { class EditTodoListBox : public Ui::BoxContent {
public: public:
struct Result { struct Result {
TodoListData todolist; TodoListData todolist;
Api::SendOptions options; Api::SendOptions options;
}; };
CreateTodoListBox( EditTodoListBox(
QWidget*, QWidget*,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
rpl::producer<int> starsRequired, rpl::producer<int> starsRequired,
Api::SendType sendType, Api::SendType sendType,
SendMenu::Details sendMenuDetails); SendMenu::Details sendMenuDetails);
EditTodoListBox(
QWidget*,
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item);
[[nodiscard]] rpl::producer<Result> submitRequests() const; [[nodiscard]] rpl::producer<Result> submitRequests() const;
void submitFailed(const QString &error); void submitFailed(const QString &error);
@ -68,6 +72,7 @@ private:
const not_null<Window::SessionController*> _controller; const not_null<Window::SessionController*> _controller;
const Api::SendType _sendType = Api::SendType(); const Api::SendType _sendType = Api::SendType();
const Fn<SendMenu::Details()> _sendMenuDetails; const Fn<SendMenu::Details()> _sendMenuDetails;
HistoryItem *_editingItem = nullptr;
rpl::variable<int> _starsRequired; rpl::variable<int> _starsRequired;
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel; base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
Fn<void()> _setInnerFocus; Fn<void()> _setInnerFocus;

View file

@ -2367,6 +2367,10 @@ TextForMimeData MediaTodoList::clipboardText() const {
return TextForMimeData::Rich(std::move(result)); return TextForMimeData::Rich(std::move(result));
} }
bool MediaTodoList::allowsEdit() const {
return parent()->out();
}
bool MediaTodoList::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaTodoList::updateInlineResultMedia(const MTPMessageMedia &media) {
return false; return false;
} }

View file

@ -625,6 +625,7 @@ public:
TextWithEntities notificationText() const override; TextWithEntities notificationText() const override;
QString pinnedTextSubstring() const override; QString pinnedTextSubstring() const override;
TextForMimeData clipboardText() const override; TextForMimeData clipboardText() const override;
bool allowsEdit() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override;

View file

@ -685,7 +685,8 @@ bool PeerData::canCreatePolls() const {
} }
bool PeerData::canCreateTodoLists() const { bool PeerData::canCreateTodoLists() const {
return Data::CanSend(this, ChatRestriction::SendPolls) || isUser(); return session().premium()
&& (Data::CanSend(this, ChatRestriction::SendPolls) || isUser());
} }
bool PeerData::canCreateTopics() const { bool PeerData::canCreateTopics() const {

View file

@ -8559,6 +8559,11 @@ void HistoryWidget::editMessage(
} else if (_voiceRecordBar->isActive()) { } else if (_voiceRecordBar->isActive()) {
controller()->showToast(tr::lng_edit_caption_voice(tr::now)); controller()->showToast(tr::lng_edit_caption_voice(tr::now));
return; return;
} else if (const auto media = item->media()) {
if (const auto todolist = media->todolist()) {
Window::PeerMenuEditTodoList(controller(), item);
return;
}
} else if (_composeSearch) { } else if (_composeSearch) {
_composeSearch->hideAnimated(); _composeSearch->hideAnimated();
} }

View file

@ -84,6 +84,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/spoiler_mess.h" #include "ui/effects/spoiler_mess.h"
#include "webrtc/webrtc_environment.h" #include "webrtc/webrtc_environment.h"
#include "window/window_adaptive.h" #include "window/window_adaptive.h"
#include "window/window_peer_menu.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
@ -2952,6 +2953,12 @@ void ComposeControls::editMessage(not_null<HistoryItem*> item) {
if (_voiceRecordBar->isActive()) { if (_voiceRecordBar->isActive()) {
_show->showBox(Ui::MakeInformBox(tr::lng_edit_caption_voice())); _show->showBox(Ui::MakeInformBox(tr::lng_edit_caption_voice()));
return; return;
} else if (const auto media = item->media()) {
if (const auto todolist = media->todolist()) {
Assert(_regularWindow != nullptr);
Window::PeerMenuEditTodoList(_regularWindow, item);
return;
}
} }
if (!isEditingMessage()) { if (!isEditingMessage()) {

View file

@ -352,6 +352,8 @@ ChatWidget::ChatWidget(
_composeControls->editMessage( _composeControls->editMessage(
fullId, fullId,
_inner->getSelectedTextRange(item)); _inner->getSelectedTextRange(item));
} else if (media->todolist()) {
Window::PeerMenuEditTodoList(controller, item);
} }
} }
}, _inner->lifetime()); }, _inner->lifetime());

View file

@ -231,6 +231,8 @@ ScheduledWidget::ScheduledWidget(
_composeControls->editMessage( _composeControls->editMessage(
fullId, fullId,
_inner->getSelectedTextRange(item)); _inner->getSelectedTextRange(item));
} else if (media->todolist()) {
Window::PeerMenuEditTodoList(controller, item);
} }
} }
}, _inner->lifetime()); }, _inner->lifetime());

View file

@ -58,6 +58,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.h" #include "ui/painter.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "window/section_widget.h" #include "window/section_widget.h"
#include "window/window_peer_menu.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
@ -399,6 +400,8 @@ ShortcutMessages::ShortcutMessages(
_composeControls->editMessage( _composeControls->editMessage(
fullId, fullId,
_inner->getSelectedTextRange(item)); _inner->getSelectedTextRange(item));
} else if (media->todolist()) {
Window::PeerMenuEditTodoList(_controller, item);
} }
} }
}, _inner->lifetime()); }, _inner->lifetime());

View file

@ -29,7 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/moderate_messages_box.h" #include "boxes/moderate_messages_box.h"
#include "boxes/choose_filter_box.h" #include "boxes/choose_filter_box.h"
#include "boxes/create_poll_box.h" #include "boxes/create_poll_box.h"
#include "boxes/create_todo_list_box.h" #include "boxes/edit_todo_list_box.h"
#include "boxes/pin_messages_box.h" #include "boxes/pin_messages_box.h"
#include "boxes/premium_limits_box.h" #include "boxes/premium_limits_box.h"
#include "boxes/report_messages_box.h" #include "boxes/report_messages_box.h"
@ -1244,7 +1244,8 @@ void Filler::addCreateTodoList() {
return; return;
} }
const auto can = _topic const auto can = _topic
? Data::CanSend(_topic, ChatRestriction::SendPolls) ? (_peer->session().premium()
&& Data::CanSend(_topic, ChatRestriction::SendPolls))
: _peer->canCreateTodoLists(); : _peer->canCreateTodoLists();
if (!can) { if (!can) {
return; return;
@ -1973,19 +1974,19 @@ void PeerMenuCreateTodoList(
) | rpl::map([=] { ) | rpl::map([=] {
return peer->starsPerMessageChecked(); return peer->starsPerMessageChecked();
}); });
auto box = Box<CreateTodoListBox>( auto box = Box<EditTodoListBox>(
controller, controller,
std::move(starsRequired), std::move(starsRequired),
sendType, sendType,
sendMenuDetails); sendMenuDetails);
struct State { struct State {
Fn<void(const CreateTodoListBox::Result &)> create; Fn<void(const EditTodoListBox::Result &)> create;
SendPaymentHelper sendPayment; SendPaymentHelper sendPayment;
bool lock = false; bool lock = false;
}; };
const auto weak = QPointer<CreateTodoListBox>(box); const auto weak = QPointer<EditTodoListBox>(box);
const auto state = box->lifetime().make_state<State>(); const auto state = box->lifetime().make_state<State>();
state->create = [=](const CreateTodoListBox::Result &result) { state->create = [=](const EditTodoListBox::Result &result) {
const auto withPaymentApproved = crl::guard(weak, [=](int stars) { const auto withPaymentApproved = crl::guard(weak, [=](int stars) {
if (const auto onstack = state->create) { if (const auto onstack = state->create) {
auto copy = result; auto copy = result;
@ -2028,6 +2029,34 @@ void PeerMenuCreateTodoList(
controller->show(std::move(box), Ui::LayerOption::CloseOther); controller->show(std::move(box), Ui::LayerOption::CloseOther);
} }
void PeerMenuEditTodoList(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item) {
const auto media = item->media();
const auto todolist = media ? media->todolist() : nullptr;
if (!todolist) {
return;
} else if (!item->history()->session().premium()) {
PeerMenuTodoWantsPremium(TodoWantsPremium::Add);
return;
}
auto box = Box<EditTodoListBox>(controller, item);
const auto weak = QPointer<EditTodoListBox>(box);
box->submitRequests(
) | rpl::start_with_next([=](const EditTodoListBox::Result &result) {
const auto api = &item->history()->session().api();
api->todoLists().edit(
item,
result.todolist,
result.options,
crl::guard(weak, [=] { weak->closeBox(); }),
crl::guard(weak, [=](const QString &error) {
weak->submitFailed(error);
}));
}, box->lifetime());
controller->show(std::move(box), Ui::LayerOption::CloseOther);
}
bool PeerMenuShowAddTodoListTasks(not_null<HistoryItem*> item) { bool PeerMenuShowAddTodoListTasks(not_null<HistoryItem*> item) {
const auto media = item ? item->media() : nullptr; const auto media = item ? item->media() : nullptr;
const auto todolist = media ? media->todolist() : nullptr; const auto todolist = media ? media->todolist() : nullptr;

View file

@ -123,6 +123,9 @@ void PeerMenuCreateTodoList(
FullReplyTo replyTo = FullReplyTo(), FullReplyTo replyTo = FullReplyTo(),
Api::SendType sendType = Api::SendType::Normal, Api::SendType sendType = Api::SendType::Normal,
SendMenu::Details sendMenuDetails = SendMenu::Details()); SendMenu::Details sendMenuDetails = SendMenu::Details());
void PeerMenuEditTodoList(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item);
[[nodiscard]] bool PeerMenuShowAddTodoListTasks(not_null<HistoryItem*> item); [[nodiscard]] bool PeerMenuShowAddTodoListTasks(not_null<HistoryItem*> item);
void PeerMenuAddTodoListTasks( void PeerMenuAddTodoListTasks(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,