From d83a80ec53684b91ca80c0359138346bedd0a644 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 12 Jun 2025 14:39:25 +0400 Subject: [PATCH] Improve adding tasks to todo lists. --- .../boxes/create_todo_list_box.cpp | 78 +++++++++++-------- .../history/history_inner_widget.cpp | 6 +- .../view/history_view_context_menu.cpp | 29 ++++++- .../SourceFiles/window/window_peer_menu.cpp | 16 ++++ .../SourceFiles/window/window_peer_menu.h | 1 + 5 files changed, 93 insertions(+), 37 deletions(-) diff --git a/Telegram/SourceFiles/boxes/create_todo_list_box.cpp b/Telegram/SourceFiles/boxes/create_todo_list_box.cpp index 846c3aabea..ec20b1bfff 100644 --- a/Telegram/SourceFiles/boxes/create_todo_list_box.cpp +++ b/Telegram/SourceFiles/boxes/create_todo_list_box.cpp @@ -72,7 +72,7 @@ public: [[nodiscard]] std::vector toTodoListItems() const; void focusFirst(); - [[nodiscard]] rpl::producer usedCount() const; + [[nodiscard]] rpl::producer addedCount() const; [[nodiscard]] rpl::producer> scrollToWidget() const; [[nodiscard]] rpl::producer<> backspaceInFront() const; [[nodiscard]] rpl::producer<> tabbed() const; @@ -162,7 +162,7 @@ private: int _tasksLimit = 0; std::vector> _list; std::vector> _destroyed; - rpl::variable _usedCount = 0; + rpl::variable _addedCount = 0; bool _hasTasks = false; bool _isValid = false; rpl::event_stream> _scrollToWidget; @@ -224,6 +224,26 @@ void FocusAtEnd(not_null field) { field->ensureCursorVisible(); } +[[nodiscard]] base::unique_qptr MakeEmojiPanel( + not_null outer, + not_null controller) { + auto result = base::make_unique_q( + outer, + controller, + object_ptr( + nullptr, + controller->uiShow(), + Window::GifPauseReason::Layer, + ChatHelpers::TabbedSelector::Mode::EmojiOnly)); + result->setDesiredHeightValues( + 1., + st::emojiPanMinHeight / 2, + st::emojiPanMinHeight); + result ->hide(); + result->selector()->setCurrentPeer(controller->session().user()); + return result; +} + Tasks::Task::Task( not_null outer, not_null container, @@ -482,8 +502,8 @@ bool Tasks::isValid() const { return _isValid; } -rpl::producer Tasks::usedCount() const { - return _usedCount.value(); +rpl::producer Tasks::addedCount() const { + return _addedCount.value(); } rpl::producer> Tasks::scrollToWidget() const { @@ -747,7 +767,7 @@ void Tasks::validateState() { _isValid = _hasTasks && ranges::none_of(_list, &Task::isTooLong); const auto lastEmpty = !_list.empty() && _list.back()->isEmpty(); - _usedCount = _list.size() + _addedCount = _list.size() - (lastEmpty ? 1 : 0) - (_existingLocked ? _existingCount : 0); } @@ -817,37 +837,22 @@ not_null CreateTodoListBox::setupTitle( title->customTab(true); if (isPremium) { - using Selector = ChatHelpers::TabbedSelector; - const auto outer = getDelegate()->outerContainer(); - _emojiPanel = base::make_unique_q( - outer, - _controller, - object_ptr( - nullptr, - _controller->uiShow(), - Window::GifPauseReason::Layer, - Selector::Mode::EmojiOnly)); - const auto emojiPanel = _emojiPanel.get(); - emojiPanel->setDesiredHeightValues( - 1., - st::emojiPanMinHeight / 2, - st::emojiPanMinHeight); - emojiPanel->hide(); - emojiPanel->selector()->setCurrentPeer(session->user()); - + _emojiPanel = MakeEmojiPanel( + getDelegate()->outerContainer(), + _controller); const auto emojiToggle = Ui::AddEmojiToggleToField( title, this, _controller, - emojiPanel, + _emojiPanel.get(), st::createPollOptionFieldPremiumEmojiPosition); - emojiPanel->selector()->emojiChosen( + _emojiPanel->selector()->emojiChosen( ) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) { if (title->hasFocus()) { Ui::InsertEmojiAtCursor(title->textCursor(), data.emoji); } }, emojiToggle->lifetime()); - emojiPanel->selector()->customEmojiChosen( + _emojiPanel->selector()->customEmojiChosen( ) | rpl::start_with_next([=](ChatHelpers::FileChosen data) { if (title->hasFocus()) { Data::InsertCustomEmoji(title, data.document); @@ -906,7 +911,7 @@ object_ptr CreateTodoListBox::setupContent() { container, _controller, _emojiPanel ? _emojiPanel.get() : nullptr); - auto limit = tasks->usedCount() | rpl::after_next([=](int count) { + auto limit = tasks->addedCount() | rpl::after_next([=](int count) { setCloseByEscape(!count); setCloseByOutsideClick(!count); }) | rpl::map([=](int count) { @@ -1094,21 +1099,32 @@ object_ptr AddTodoListTasksBox::setupContent() { auto result = object_ptr(this); const auto container = result.data(); + if (_controller->session().premium()) { + _emojiPanel = MakeEmojiPanel( + getDelegate()->outerContainer(), + _controller); + } + + const auto media = _item->media(); + const auto todolist = media ? media->todolist() : nullptr; + Assert(todolist != nullptr); const auto tasks = lifetime().make_state( this, container, _controller, _emojiPanel ? _emojiPanel.get() : nullptr, - _item->media()->todolist()->items, + todolist->items, true); - auto limit = tasks->usedCount() | rpl::after_next([=](int count) { + const auto already = int(todolist->items.size()); + auto limit = tasks->addedCount() | rpl::after_next([=](int count) { setCloseByEscape(!count); setCloseByOutsideClick(!count); }) | rpl::map([=](int count) { const auto appConfig = &_controller->session().appConfig(); const auto max = appConfig->todoListItemsLimit(); - return (count < max) - ? tr::lng_todo_create_limit(tr::now, lt_count, max - count) + const auto total = already + count; + return (total < max) + ? tr::lng_todo_create_limit(tr::now, lt_count, max - total) : tr::lng_todo_create_maximum(tr::now); }) | rpl::after_next([=] { container->resizeToWidth(container->widthNoMargins()); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index b00d999063..3f8b82b933 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2721,11 +2721,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }; const auto addTodoListAction = [&](HistoryItem *item) { - const auto media = item ? item->media() : nullptr; - const auto todolist = media ? media->todolist() : nullptr; - if (!todolist - || !item->isRegular() - || (!item->out() && !todolist->othersCanAppend())) { + if (!item || !Window::PeerMenuShowAddTodoListTasks(item)) { return; } const auto itemId = item->fullId(); diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 58ea3e210e..a9b98a98d6 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -626,7 +626,9 @@ bool AddReplyToMessageAction( const auto peer = item ? item->history()->peer.get() : nullptr; if (!item || !item->isRegular() - || (context != Context::History && context != Context::Replies)) { + || (context != Context::History + && context != Context::Replies + && context != Context::Monoforum)) { return false; } const auto canSendReply = topic @@ -653,6 +655,30 @@ bool AddReplyToMessageAction( return true; } +bool AddTodoListAction( + not_null menu, + const ContextMenuRequest &request, + not_null list) { + const auto context = list->elementContext(); + const auto item = request.item; + if (!item + || !Window::PeerMenuShowAddTodoListTasks(item) + || (context != Context::History + && context != Context::Replies + && context != Context::Monoforum + && context != Context::Pinned)) { + return false; + } + const auto itemId = item->fullId(); + const auto controller = list->controller(); + menu->addAction(tr::lng_todo_add_title(tr::now), [=] { + if (const auto item = controller->session().data().message(itemId)) { + Window::PeerMenuAddTodoListTasks(controller, item); + } + }, &st::menuIconCreateTodoList); + return true; +} + bool AddViewRepliesAction( not_null menu, const ContextMenuRequest &request, @@ -1281,6 +1307,7 @@ base::unique_qptr FillContextMenu( st::popupMenuWithIcons); AddReplyToMessageAction(result, request, list); + AddTodoListAction(result, request, list); if (request.overSelection && !list->hasCopyRestrictionForSelected() diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index bb9604b89d..6457792767 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -51,6 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/delayed_activation.h" #include "ui/vertical_list.h" #include "ui/ui_utility.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "main/main_session_settings.h" #include "menu/menu_mute.h" @@ -2027,6 +2028,16 @@ void PeerMenuCreateTodoList( controller->show(std::move(box), Ui::LayerOption::CloseOther); } +bool PeerMenuShowAddTodoListTasks(not_null item) { + const auto media = item ? item->media() : nullptr; + const auto todolist = media ? media->todolist() : nullptr; + const auto appConfig = &item->history()->session().appConfig(); + return item->isRegular() + && todolist + && (todolist->items.size() < appConfig->todoListItemsLimit()) + && (item->out() || todolist->othersCanAppend()); +} + void PeerMenuAddTodoListTasks( not_null controller, not_null item) { @@ -2035,6 +2046,11 @@ void PeerMenuAddTodoListTasks( PeerMenuTodoWantsPremium(TodoWantsPremium::Add); return; } + const auto media = item->media(); + const auto todolist = media ? media->todolist() : nullptr; + if (!todolist) { + return; + } auto box = Box(controller, item); const auto raw = box.data(); box->submitRequests( diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index 4b5419ff8a..961ee18e86 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -123,6 +123,7 @@ void PeerMenuCreateTodoList( FullReplyTo replyTo = FullReplyTo(), Api::SendType sendType = Api::SendType::Normal, SendMenu::Details sendMenuDetails = SendMenu::Details()); +[[nodiscard]] bool PeerMenuShowAddTodoListTasks(not_null item); void PeerMenuAddTodoListTasks( not_null controller, not_null item);