diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index cf06a30026..fe9caf683b 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2946,8 +2946,6 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { ? tr::lng_context_quote_and_reply : todoListTaskId ? tr::lng_context_reply_to_task - : !pollOptionLink.isEmpty() - ? tr::lng_context_reply_to_poll_option : tr::lng_context_reply_msg)( tr::now, Ui::Text::FixAmpersandInAction); @@ -2959,7 +2957,6 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { .quote = selected.highlight.quote, .quoteOffset = selected.highlight.quoteOffset, .todoItemId = todoListTaskId, - .pollOption = pollOptionLink, }); if (!selected.highlight.quote.empty()) { _widget->clearSelected(); @@ -2990,53 +2987,6 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }), &st::menuIconAdd); }; - const auto addCopyPollOptionAction = [&](HistoryItem *item) { - if (pollOptionLink.isEmpty() || !item) { - return; - } - if (const auto media = item->media()) { - if (const auto poll = media->poll()) { - if (const auto a = poll->answerByOption(pollOptionLink)) { - auto text = a->text; - _menu->addAction( - tr::lng_context_copy_poll_option(tr::now), - [text = TextForMimeData::Rich(std::move(text))] { - TextUtilities::SetClipboardText(text); - }, - &st::menuIconCopy); - const auto canDelete = [&] { - if (!a->addedDate) { - return false; - } - if (poll->creator()) { - return true; - } - if (a->addedBy - && a->addedBy->isSelf()) { - const auto period = poll->session() - .appConfig() - .pollAnswerDeletePeriod(); - return (base::unixtime::now() - a->addedDate) - < period; - } - return false; - }(); - if (canDelete) { - const auto itemId = item->fullId(); - const auto option = pollOptionLink; - _menu->addAction( - tr::lng_context_delete_poll_option(tr::now), - [=] { - poll->session().api().polls().deleteAnswer( - itemId, - option); - }, - &st::menuIconDelete); - } - } - } - } - }; const auto lnkPhoto = link ? reinterpret_cast( link->property(kPhotoLinkMediaProperty).toULongLong()) @@ -3049,7 +2999,6 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto item = _dragStateItem; const auto itemId = item ? item->fullId() : FullMsgId(); addReplyAction(item); - addCopyPollOptionAction(item); if (isUponSelected > 0) { const auto selectedText = getSelectedText(); @@ -3188,8 +3137,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } if (isUponSelected > 0) { addReplyAction(item); - addCopyPollOptionAction(item); - const auto selectedText = getSelectedText(); + const auto selectedText = getSelectedText(); if (!hasCopyRestrictionForSelected() && !selectedText.empty()) { _menu->addAction( ((isUponSelected > 1) @@ -3212,8 +3160,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { addItemActions(item, item); } else { addReplyAction(partItemOrLeader); - addCopyPollOptionAction(partItemOrLeader); - addTodoListAction(partItemOrLeader); + addTodoListAction(partItemOrLeader); addItemActions(item, albumPartItem); if (item && !isUponSelected) { const auto media = (view ? view->media() : nullptr); @@ -3434,48 +3381,6 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { textItem ? textItem : _dragStateItem, !added); } - if (!pollOptionLink.isEmpty() && leaderOrSelf) { - if (const auto media = leaderOrSelf->media()) { - if (const auto poll = media->poll()) { - if (const auto a = poll->answerByOption(pollOptionLink)) { - if (a->addedBy) { - if (!_menu->empty()) { - _menu->addSeparator( - &st::expandedMenuSeparator); - } - const auto photoSize - = st::defaultWhoRead.photoSize; - auto view = Ui::PeerUserpicView(); - auto userpic - = PeerData::GenerateUserpicImage( - a->addedBy, - view, - photoSize); - const auto date = a->addedDate - ? Ui::FormatDateTime( - base::unixtime::parse(a->addedDate)) - : QString(); - _menu->addAction( - base::make_unique_q< - Ui::WhoReactedEntryAction>( - _menu->menu(), - nullptr, - _menu->menu()->st(), - Ui::WhoReactedEntryData{ - .text = tr::lng_polls_option_added_by( - tr::now, - lt_user, - a->addedBy->shortName()), - .date = date, - .type = Ui::WhoReactedType - ::RefRecipient, - .userpic = std::move(userpic), - })); - } - } - } - } - } if (hasWhoReactedItem) { HistoryView::AddWhoReactedAction( _menu, @@ -3496,6 +3401,23 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { Menu::RateTranscribeCallbackFactory(rateTranscriptionItem))); } + if (!pollOptionLink.isEmpty() && leaderOrSelf && !_menu->empty()) { + const auto pollItemId = leaderOrSelf->fullId(); + _menu->stashContent([=](not_null menu) { + HistoryView::FillPollOptionPage( + menu, + &session->data(), + pollItemId, + pollOptionLink, + [=] { + _widget->replyToMessage({ + .messageId = pollItemId, + .pollOption = pollOptionLink, + }); + }); + }); + } + if (_menu->empty()) { _menu = nullptr; return; @@ -3517,7 +3439,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (attached == AttachSelectorResult::Failed) { _menu = nullptr; return; - } else if (attached == AttachSelectorResult::Attached) { + } + HistoryView::AttachPollOptionTabs(_menu.get(), desiredPosition); + if (attached == AttachSelectorResult::Attached) { _menu->popupPrepared(); } else { _menu->popup(desiredPosition); diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index b9813a30ea..979bcdb0e8 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -45,6 +45,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/edit_factcheck_box.h" #include "ui/boxes/report_box_graphics.h" #include "ui/ui_utility.h" +#include "ui/painter.h" +#include "ui/widgets/pill_tabs.h" #include "menu/menu_item_download_files.h" #include "menu/menu_item_rate_transcribe.h" #include "menu/menu_item_rate_transcribe_session.h" @@ -626,14 +628,9 @@ bool AddReplyToMessageAction( const auto todoListTaskId = request.link ? request.link->property(kTodoListItemIdProperty).toInt() : 0; - const auto pollOption = request.link - ? request.link->property(kPollOptionProperty).toByteArray() - : QByteArray(); const auto "e = request.quote; auto text = (todoListTaskId ? tr::lng_context_reply_to_task - : !pollOption.isEmpty() - ? tr::lng_context_reply_to_poll_option : quote.highlight.quote.empty() ? tr::lng_context_reply_msg : tr::lng_context_quote_and_reply)( @@ -645,7 +642,6 @@ bool AddReplyToMessageAction( .quote = quote.highlight.quote, .quoteOffset = quote.highlight.quoteOffset, .todoItemId = todoListTaskId, - .pollOption = pollOption, }, base::IsCtrlPressed()); }, &st::menuIconReply); return true; @@ -1282,9 +1278,11 @@ ContextMenuRequest::ContextMenuRequest( : navigation(navigation) { } -base::unique_qptr FillContextMenu( +void FillContextMenuItems( + not_null result, not_null list, - const ContextMenuRequest &request) { + const ContextMenuRequest &request, + bool skipWhoReacted = false) { const auto link = request.link; const auto view = request.view; const auto item = request.item; @@ -1305,58 +1303,7 @@ base::unique_qptr FillContextMenu( const auto hasWhoReactedItem = item && Api::WhoReactedExists(item, Api::WhoReactedList::All); - auto result = base::make_unique_q( - list, - st::popupMenuWithIcons); - AddReplyToMessageAction(result, request, list); - if (const auto pollOption = link - ? link->property(kPollOptionProperty).toByteArray() - : QByteArray(); !pollOption.isEmpty()) { - if (item) { - if (const auto media = item->media()) { - if (const auto poll = media->poll()) { - if (const auto a = poll->answerByOption(pollOption)) { - auto text = a->text; - result->addAction( - tr::lng_context_copy_poll_option(tr::now), - [text = TextForMimeData::Rich(std::move(text))] { - TextUtilities::SetClipboardText(text); - }, - &st::menuIconCopy); - const auto canDelete = [&] { - if (!a->addedDate) { - return false; - } - if (poll->creator()) { - return true; - } - if (a->addedBy - && a->addedBy->isSelf()) { - const auto period = poll->session() - .appConfig() - .pollAnswerDeletePeriod(); - return (base::unixtime::now() - a->addedDate) - < period; - } - return false; - }(); - if (canDelete) { - const auto itemId = item->fullId(); - result->addAction( - tr::lng_context_delete_poll_option(tr::now), - [=] { - poll->session().api().polls().deleteAnswer( - itemId, - pollOption); - }, - &st::menuIconDelete); - } - } - } - } - } - } AddTodoListAction(result, request, list); if (request.overSelection @@ -1458,47 +1405,48 @@ base::unique_qptr FillContextMenu( const auto added = (result->actions().size() > wasAmount); AddSelectRestrictionAction(result, item, !added); } - if (const auto pollOption = link - ? link->property(kPollOptionProperty).toByteArray() - : QByteArray(); !pollOption.isEmpty() && poll) { - if (const auto a = poll->answerByOption(pollOption)) { - if (a->addedBy) { - if (!result->empty()) { - result->addSeparator(&st::expandedMenuSeparator); - } - const auto photoSize = st::defaultWhoRead.photoSize; - auto view = Ui::PeerUserpicView(); - auto userpic = PeerData::GenerateUserpicImage( - a->addedBy, - view, - photoSize); - const auto date = a->addedDate - ? Ui::FormatDateTime( - base::unixtime::parse(a->addedDate)) - : QString(); - result->addAction( - base::make_unique_q( - result->menu(), - nullptr, - result->menu()->st(), - Ui::WhoReactedEntryData{ - .text = tr::lng_polls_option_added_by( - tr::now, - lt_user, - a->addedBy->shortName()), - .date = date, - .type = Ui::WhoReactedType::RefRecipient, - .userpic = std::move(userpic), - })); - } + if (!skipWhoReacted) { + if (hasWhoReactedItem) { + AddWhoReactedAction(result, list, item, list->controller()); + } else if (item) { + MaybeAddWhenEditedForwardedAction( + result, + item, + list->controller()); } } - if (hasWhoReactedItem) { - AddWhoReactedAction(result, list, item, list->controller()); - } else if (item) { - MaybeAddWhenEditedForwardedAction(result, item, list->controller()); - } +} +base::unique_qptr FillContextMenu( + not_null list, + const ContextMenuRequest &request) { + const auto link = request.link; + const auto item = request.item; + const auto itemId = item ? item->fullId() : FullMsgId(); + const auto pollOption = link + ? link->property(kPollOptionProperty).toByteArray() + : QByteArray(); + const auto hasPollOption = !pollOption.isEmpty() && item; + + auto result = base::make_unique_q( + list, + st::popupMenuWithIcons); + + // Build the full message menu. + FillContextMenuItems(result, list, request, hasPollOption); + + if (hasPollOption) { + const auto raw = result.get(); + const auto owner = &item->history()->owner(); + raw->stashContent([=](not_null menu) { + FillPollOptionPage(menu, owner, itemId, pollOption, [=] { + list->replyToMessageRequestNotify({ + .messageId = itemId, + .pollOption = pollOption, + }, base::IsCtrlPressed()); + }); + }); + } return result; } @@ -1577,6 +1525,176 @@ void CopyStoryLink( show->showToast(tr::lng_channel_public_link_copied(tr::now)); } +void FillPollOptionItems( + not_null menu, + not_null owner, + FullMsgId itemId, + const QByteArray &pollOption) { + const auto item = owner->message(itemId); + if (!item) { + return; + } + const auto media = item->media(); + const auto poll = media ? media->poll() : nullptr; + if (!poll) { + return; + } + const auto a = poll->answerByOption(pollOption); + if (!a) { + return; + } + auto text = a->text; + menu->addAction( + tr::lng_context_copy_poll_option(tr::now), + [text = TextForMimeData::Rich(std::move(text))] { + TextUtilities::SetClipboardText(text); + }, + &st::menuIconCopy); + const auto canDelete = [&] { + if (!a->addedDate) { + return false; + } + if (poll->creator()) { + return true; + } + if (a->addedBy && a->addedBy->isSelf()) { + const auto period = poll->session() + .appConfig() + .pollAnswerDeletePeriod(); + return (base::unixtime::now() - a->addedDate) < period; + } + return false; + }(); + if (canDelete) { + menu->addAction( + tr::lng_context_delete_poll_option(tr::now), + [=] { + if (const auto item = owner->message(itemId)) { + if (const auto media = item->media()) { + if (const auto poll = media->poll()) { + poll->session().api().polls() + .deleteAnswer(itemId, pollOption); + } + } + } + }, + &st::menuIconDelete); + } + if (a->addedBy) { + menu->addSeparator(&st::expandedMenuSeparator); + auto view = Ui::PeerUserpicView(); + auto userpic = PeerData::GenerateUserpicImage( + a->addedBy, + view, + st::defaultWhoRead.photoSize); + const auto date = a->addedDate + ? Ui::FormatDateTime( + base::unixtime::parse(a->addedDate)) + : QString(); + menu->addAction( + base::make_unique_q( + menu->menu(), + nullptr, + menu->menu()->st(), + Ui::WhoReactedEntryData{ + .text = tr::lng_polls_option_added_by( + tr::now, + lt_user, + a->addedBy->shortName()), + .date = date, + .type = Ui::WhoReactedType::RefRecipient, + .userpic = std::move(userpic), + })); + } +} + + +void FillPollOptionPage( + not_null menu, + not_null owner, + FullMsgId itemId, + const QByteArray &pollOption, + Fn replyToOption) { + if (replyToOption) { + menu->addAction( + tr::lng_context_reply_to_poll_option( + tr::now, + Ui::Text::FixAmpersandInAction), + std::move(replyToOption), + &st::menuIconReply); + } + FillPollOptionItems(menu, owner, itemId, pollOption); +} + +void AttachPollOptionTabs( + not_null menu, + QPoint desiredPosition) { + if (!menu->hasStashedContent()) { + return; + } + const auto &tabsSt = st::popupMenuPillTabs; + const auto tabs = Ui::CreateChild( + menu.get(), + std::vector{ u"Option"_q, u"Poll"_q }, + 0, + tabsSt); + + const auto height = tabsSt.height; + const auto margin = tabsSt.margin; + tabs->show(); + + // Reserve space for tabs by increasing additional padding top. + { + auto padding = menu->additionalMenuPadding(); + auto margins = menu->additionalMenuMargins(); + padding.setTop(padding.top() + height + margin); + menu->setAdditionalMenuPadding(padding, margins); + menu->prepareGeometryFor(desiredPosition); + } + + // Position tabs just above _inner (in the reserved padding space). + const auto reposition = [=] { + const auto inner = menu->inner(); + tabs->setGeometry( + inner.x(), + inner.y() - height - margin, + inner.width(), + height); + }; + reposition(); + + // Wire tab changes to swap stashed content. + tabs->activeIndexChanges( + ) | rpl::on_next([=](int index) { + const auto direction = (index > 0) + ? Ui::PopupMenu::SwitchDirection::LeftToRight + : Ui::PopupMenu::SwitchDirection::RightToLeft; + base::call_delayed(0, menu, [=] { + menu->swapStashed(direction); + }); + }, tabs->lifetime()); + + // Reposition after geometry is prepared. + menu->animatePhaseValue( + ) | rpl::on_next([=](Ui::PopupMenu::AnimatePhase phase) { + if (phase == Ui::PopupMenu::AnimatePhase::StartShow) { + reposition(); + } + }, tabs->lifetime()); + + menu->showStateValue( + ) | rpl::on_next([=](Ui::PopupMenu::ShowState state) { + if (state.appearing) { + tabs->setVisible(state.heightProgress > 0.); + tabs->setOpacity(state.opacity * state.heightProgress); + } else { + tabs->setVisible(true); + tabs->setOpacity(1.); + reposition(); + } + }, tabs->lifetime()); +} + void AddPollActions( not_null menu, not_null poll, diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.h b/Telegram/SourceFiles/history/view/history_view_context_menu.h index fb26345500..4f66f8d4e2 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.h +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" namespace Data { +class Session; struct ReactionId; } // namespace Data @@ -70,6 +71,17 @@ void CopyPostLink( void CopyStoryLink( std::shared_ptr show, FullStoryId storyId); +void FillPollOptionPage( + not_null menu, + not_null owner, + FullMsgId itemId, + const QByteArray &pollOption, + Fn replyToOption = nullptr); + +void AttachPollOptionTabs( + not_null menu, + QPoint desiredPosition); + void AddPollActions( not_null menu, not_null poll, diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index cadee423b3..ee8fa6f679 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -3078,7 +3078,9 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (attached == AttachSelectorResult::Failed) { _menu = nullptr; return; - } else if (attached == AttachSelectorResult::Attached) { + } + HistoryView::AttachPollOptionTabs(_menu.get(), desiredPosition); + if (attached == AttachSelectorResult::Attached) { _menu->popupPrepared(); } else { _menu->popup(desiredPosition);