Improve adding tasks to todo lists.

This commit is contained in:
John Preston 2025-06-12 14:39:25 +04:00
parent 248fe1b53f
commit d83a80ec53
5 changed files with 93 additions and 37 deletions

View file

@ -72,7 +72,7 @@ public:
[[nodiscard]] std::vector<TodoListItem> toTodoListItems() const;
void focusFirst();
[[nodiscard]] rpl::producer<int> usedCount() const;
[[nodiscard]] rpl::producer<int> addedCount() const;
[[nodiscard]] rpl::producer<not_null<QWidget*>> scrollToWidget() const;
[[nodiscard]] rpl::producer<> backspaceInFront() const;
[[nodiscard]] rpl::producer<> tabbed() const;
@ -162,7 +162,7 @@ private:
int _tasksLimit = 0;
std::vector<std::unique_ptr<Task>> _list;
std::vector<std::unique_ptr<Task>> _destroyed;
rpl::variable<int> _usedCount = 0;
rpl::variable<int> _addedCount = 0;
bool _hasTasks = false;
bool _isValid = false;
rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
@ -224,6 +224,26 @@ void FocusAtEnd(not_null<Ui::InputField*> field) {
field->ensureCursorVisible();
}
[[nodiscard]] base::unique_qptr<ChatHelpers::TabbedPanel> MakeEmojiPanel(
not_null<QWidget*> outer,
not_null<Window::SessionController*> controller) {
auto result = base::make_unique_q<ChatHelpers::TabbedPanel>(
outer,
controller,
object_ptr<ChatHelpers::TabbedSelector>(
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<QWidget*> outer,
not_null<Ui::VerticalLayout*> container,
@ -482,8 +502,8 @@ bool Tasks::isValid() const {
return _isValid;
}
rpl::producer<int> Tasks::usedCount() const {
return _usedCount.value();
rpl::producer<int> Tasks::addedCount() const {
return _addedCount.value();
}
rpl::producer<not_null<QWidget*>> 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<Ui::InputField*> CreateTodoListBox::setupTitle(
title->customTab(true);
if (isPremium) {
using Selector = ChatHelpers::TabbedSelector;
const auto outer = getDelegate()->outerContainer();
_emojiPanel = base::make_unique_q<ChatHelpers::TabbedPanel>(
outer,
_controller,
object_ptr<Selector>(
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<Ui::RpWidget> 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<Ui::RpWidget> AddTodoListTasksBox::setupContent() {
auto result = object_ptr<Ui::VerticalLayout>(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<Tasks>(
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());

View file

@ -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();

View file

@ -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<Ui::PopupMenu*> menu,
const ContextMenuRequest &request,
not_null<ListWidget*> 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<Ui::PopupMenu*> menu,
const ContextMenuRequest &request,
@ -1281,6 +1307,7 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
st::popupMenuWithIcons);
AddReplyToMessageAction(result, request, list);
AddTodoListAction(result, request, list);
if (request.overSelection
&& !list->hasCopyRestrictionForSelected()

View file

@ -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<HistoryItem*> 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<Window::SessionController*> controller,
not_null<HistoryItem*> 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<AddTodoListTasksBox>(controller, item);
const auto raw = box.data();
box->submitRequests(

View file

@ -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<HistoryItem*> item);
void PeerMenuAddTodoListTasks(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item);