From 18d62c070d2ae09054e9d92f2b1770ab91cea62e Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 11 Feb 2021 20:10:15 +0400 Subject: [PATCH] Show auto-delete info in message context menu. --- Telegram/Resources/langs/lang.strings | 1 + .../SourceFiles/calls/calls_group_members.cpp | 2 +- .../history/history_inner_widget.cpp | 22 +- .../view/history_view_context_menu.cpp | 21 +- .../info/media/info_media_list_widget.cpp | 11 +- Telegram/SourceFiles/ui/chat/chat.style | 3 + .../delete_message_context_action.cpp | 253 ++++++++++++++++++ .../controls/delete_message_context_action.h | 26 ++ Telegram/cmake/td_ui.cmake | 2 + 9 files changed, 322 insertions(+), 19 deletions(-) create mode 100644 Telegram/SourceFiles/ui/controls/delete_message_context_action.cpp create mode 100644 Telegram/SourceFiles/ui/controls/delete_message_context_action.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2f9016e49..e1c0d72d2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1613,6 +1613,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_send_now_msg" = "Send now"; "lng_context_reschedule" = "Reschedule"; "lng_context_delete_msg" = "Delete Message"; +"lng_context_auto_delete_in" = "auto-delete in {duration}"; "lng_context_select_msg" = "Select Message"; "lng_context_report_msg" = "Report Message"; "lng_context_pin_msg" = "Pin Message"; diff --git a/Telegram/SourceFiles/calls/calls_group_members.cpp b/Telegram/SourceFiles/calls/calls_group_members.cpp index 7229dad6a..3f2ba724c 100644 --- a/Telegram/SourceFiles/calls/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/calls_group_members.cpp @@ -1414,7 +1414,7 @@ void MembersController::addMuteActionsToContextMenu( : rpl::never() | rpl::type_erased(); auto volumeItem = base::make_unique_q( - menu, + menu->menu(), st::groupCallPopupMenu.menu, otherParticipantStateValue, row->volume(), diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 495376acc..fb88bd32e 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "ui/toast/toast.h" #include "ui/text/text_options.h" +#include "ui/controls/delete_message_context_action.h" #include "ui/ui_utility.h" #include "ui/cached_round_corners.h" #include "ui/inactive_press.h" @@ -1690,9 +1691,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }); } if (item->canDelete()) { - _menu->addAction(tr::lng_context_delete_msg(tr::now), [=] { - deleteItem(itemId); - }); + _menu->addAction(Ui::DeleteMessageContextAction( + _menu->menu(), + [=] { deleteItem(itemId); }, + item->ttlDestroyAt(), + [=] { _menu = nullptr; })); } if (!blockSender && item->suggestReport()) { _menu->addAction(tr::lng_context_report_msg(tr::now), [=] { @@ -1822,9 +1825,18 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }); } if (canDelete) { - _menu->addAction((msg && msg->uploading()) ? tr::lng_context_cancel_upload(tr::now) : tr::lng_context_delete_msg(tr::now), [=] { + const auto callback = [=] { deleteAsGroup(itemId); - }); + }; + if (msg && msg->uploading()) { + _menu->addAction(tr::lng_context_cancel_upload(tr::now), callback); + } else { + _menu->addAction(Ui::DeleteMessageContextAction( + _menu->menu(), + callback, + item->ttlDestroyAt(), + [=] { _menu = nullptr; })); + } } if (!canBlockSender && canReport) { _menu->addAction(tr::lng_context_report_msg(tr::now), [=] { diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 73ca05504..bf2c7743a 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/popup_menu.h" #include "ui/image/image.h" #include "ui/toast/toast.h" +#include "ui/controls/delete_message_context_action.h" #include "ui/ui_utility.h" #include "chat_helpers/send_context_menu.h" #include "boxes/confirm_box.h" @@ -740,15 +741,19 @@ bool AddDeleteMessageAction( Ui::show(Box(item, suggestModerateActions)); } }); - const auto text = [&] { - if (const auto message = item->toHistoryMessage()) { - if (message->uploading()) { - return tr::lng_context_cancel_upload; - } + if (const auto message = item->toHistoryMessage()) { + if (message->uploading()) { + menu->addAction( + tr::lng_context_cancel_upload(tr::now), + callback); + return true; } - return tr::lng_context_delete_msg; - }()(tr::now); - menu->addAction(text, callback); + } + menu->addAction(Ui::DeleteMessageContextAction( + menu->menu(), + callback, + item->ttlDestroyAt(), + [=] { delete menu; })); return true; } diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp index be74d7251..0670b44b7 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "window/window_peer_menu.h" #include "ui/widgets/popup_menu.h" +#include "ui/controls/delete_message_context_action.h" #include "ui/ui_utility.h" #include "ui/inactive_press.h" #include "lang/lang_keys.h" @@ -1414,11 +1415,11 @@ void ListWidget::showContextMenu( })); } if (item->canDelete()) { - _contextMenu->addAction( - tr::lng_context_delete_msg(tr::now), - crl::guard(this, [this, universalId] { - deleteItem(universalId); - })); + _contextMenu->addAction(Ui::DeleteMessageContextAction( + _contextMenu->menu(), + [=] { deleteItem(universalId); }, + item->ttlDestroyAt(), + [=] { _contextMenu = nullptr; })); } } _contextMenu->addAction( diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index dd43299bf..7b7e0475f 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -877,3 +877,6 @@ defaultSliderForTTL: SliderForTTL { dashOff: 5px; } ttlDividerLabelPadding: margins(22px, 10px, 22px, 19px); + +ttlItemPadding: margins(0px, 4px, 0px, 5px); +ttlItemTimerFont: font(11px); diff --git a/Telegram/SourceFiles/ui/controls/delete_message_context_action.cpp b/Telegram/SourceFiles/ui/controls/delete_message_context_action.cpp new file mode 100644 index 000000000..bda1c0436 --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/delete_message_context_action.cpp @@ -0,0 +1,253 @@ +/* +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 "ui/controls/delete_message_context_action.h" + +#include "ui/widgets/menu/menu_action.h" +#include "ui/effects/ripple_animation.h" +#include "lang/lang_keys.h" +#include "base/call_delayed.h" +#include "base/unixtime.h" +#include "base/timer.h" +#include "styles/style_chat.h" + +namespace Ui { +namespace { + +class ActionWithTimer final : public Menu::ItemBase { +public: + ActionWithTimer( + not_null parent, + const style::Menu &st, + TimeId destroyAt, + Fn callback, + Fn destroyByTimerCallback); + + bool isEnabled() const override; + not_null action() const override; + + void handleKeyPress(not_null e) override; + +protected: + QPoint prepareRippleStartPosition() const override; + QImage prepareRippleMask() const override; + + int contentHeight() const override; + +private: + void prepare(); + void refreshAutoDeleteText(); + void paint(Painter &p); + + const not_null _dummyAction; + const style::Menu &_st; + const TimeId _destroyAt = 0; + const Fn _destroyByTimerCallback; + const crl::time _startedAt = 0; + crl::time _lastCheckAt = 0; + base::Timer _refreshTimer; + + Text::String _text; + int _textWidth = 0; + QString _autoDeleteText; + const int _height; + +}; + +TextParseOptions MenuTextOptions = { + TextParseLinks | TextParseRichText, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir +}; + +ActionWithTimer::ActionWithTimer( + not_null parent, + const style::Menu &st, + TimeId destroyAt, + Fn callback, + Fn destroyByTimerCallback) +: ItemBase(parent, st) +, _dummyAction(new QAction(parent)) +, _st(st) +, _destroyAt(destroyAt) +, _destroyByTimerCallback(destroyByTimerCallback) +, _startedAt(crl::now()) +, _refreshTimer([=] { refreshAutoDeleteText(); }) +, _height(st::ttlItemPadding.top() + + _st.itemStyle.font->height + + st::ttlItemTimerFont->height + + st::ttlItemPadding.bottom()) { + setAcceptBoth(true); + initResizeHook(parent->sizeValue()); + setClickedCallback(std::move(callback)); + + paintRequest( + ) | rpl::start_with_next([=] { + Painter p(this); + paint(p); + }, lifetime()); + + enableMouseSelecting(); + prepare(); +} + +void ActionWithTimer::paint(Painter &p) { + const auto selected = isSelected(); + if (selected && _st.itemBgOver->c.alpha() < 255) { + p.fillRect(0, 0, width(), _height, _st.itemBg); + } + p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg); + if (isEnabled()) { + paintRipple(p, 0, 0); + } + p.setPen(selected ? _st.itemFgOver : _st.itemFg); + _text.drawLeftElided( + p, + _st.itemPadding.left(), + st::ttlItemPadding.top(), + _textWidth, + width()); + + p.setPen(selected ? _st.itemFgShortcutOver : _st.itemFgShortcut); + p.drawTextLeft( + _st.itemPadding.left(), + st::ttlItemPadding.top() + _st.itemStyle.font->height, + width(), + _autoDeleteText); +} + +void ActionWithTimer::refreshAutoDeleteText() { + const auto now = base::unixtime::now(); + const auto left = (_destroyAt > now) ? (_destroyAt - now) : 0; + const auto text = [&] { + const auto duration = (left >= 86400) + ? tr::lng_group_call_duration_days( + tr::now, + lt_count, + ((left + 43200) / 86400)) + : (left >= 3600) + ? QString("%1:%2:%3" + ).arg(left / 3600 + ).arg((left % 3600) / 60, 2, 10, QChar('0') + ).arg(left % 60, 2, 10, QChar('0')) + : QString("%1:%2" + ).arg(left / 60 + ).arg(left % 60, 2, 10, QChar('0')); + return tr::lng_context_auto_delete_in( + tr::now, + lt_duration, + duration); + }(); + if (_autoDeleteText != text) { + _autoDeleteText = text; + update(); + } + + if (!left) { + base::call_delayed(crl::time(100), this, _destroyByTimerCallback); + return; + } + const auto nextCall = (left >= 86400) + ? ((left % 43200) + 1) * crl::time(1000) + : crl::time(500) - ((crl::now() - _startedAt) % 500); + _refreshTimer.callOnce(nextCall); +} + +void ActionWithTimer::prepare() { + refreshAutoDeleteText(); + + _text.setMarkedText( + _st.itemStyle, + { tr::lng_context_delete_msg(tr::now) }, + MenuTextOptions); + const auto textWidth = _text.maxWidth(); + const auto &padding = _st.itemPadding; + + const auto goodWidth = padding.left() + + textWidth + + padding.right(); + const auto ttlMaxWidth = [&](const QString &duration) { + return padding.left() + + st::ttlItemTimerFont->width(tr::lng_context_auto_delete_in( + tr::now, + lt_duration, + duration)) + + padding.right(); + }; + const auto maxWidth1 = ttlMaxWidth("23:59:59"); + const auto maxWidth2 = ttlMaxWidth(tr::lng_group_call_duration_days( + tr::now, + lt_count, + 7)); + + const auto w = std::clamp( + std::max({ goodWidth, maxWidth1, maxWidth2 }), + _st.widthMin, + _st.widthMax); + _textWidth = w - (goodWidth - textWidth); + setMinWidth(w); + update(); +} + +bool ActionWithTimer::isEnabled() const { + return true; +} + +not_null ActionWithTimer::action() const { + return _dummyAction; +} + +QPoint ActionWithTimer::prepareRippleStartPosition() const { + return mapFromGlobal(QCursor::pos()); +} + +QImage ActionWithTimer::prepareRippleMask() const { + return Ui::RippleAnimation::rectMask(size()); +} + +int ActionWithTimer::contentHeight() const { + return _height; +} + +void ActionWithTimer::handleKeyPress(not_null e) { + if (!isSelected()) { + return; + } + const auto key = e->key(); + if (key == Qt::Key_Enter || key == Qt::Key_Return) { + setClicked(Menu::TriggeredSource::Keyboard); + } +} + +} // namespace + +base::unique_qptr DeleteMessageContextAction( + not_null menu, + Fn callback, + TimeId destroyAt, + Fn destroyByTimerCallback) { + if (destroyAt <= 0) { + return base::make_unique_q( + menu, + menu->st(), + Menu::CreateAction( + menu, + tr::lng_context_delete_msg(tr::now), + std::move(callback)), + nullptr, + nullptr); + } + return base::make_unique_q( + menu, + menu->st(), + destroyAt, + std::move(callback), + std::move(destroyByTimerCallback)); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/delete_message_context_action.h b/Telegram/SourceFiles/ui/controls/delete_message_context_action.h new file mode 100644 index 000000000..3ed2c4f0b --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/delete_message_context_action.h @@ -0,0 +1,26 @@ +/* +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 +*/ +#pragma once + +#include "base/unique_qptr.h" + +namespace Ui { +namespace Menu { +class Menu; +class ItemBase; +} // namespace Menu + +class PopupMenu; + +[[nodiscard]] base::unique_qptr DeleteMessageContextAction( + not_null menu, + Fn callback, + TimeId destroyAt, + Fn destroyByTimerCallback); + +} // namespace Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 73528d4dc..60acbe619 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -94,6 +94,8 @@ PRIVATE ui/chat/message_bar.h ui/chat/pinned_bar.cpp ui/chat/pinned_bar.h + ui/controls/delete_message_context_action.cpp + ui/controls/delete_message_context_action.h ui/controls/emoji_button.cpp ui/controls/emoji_button.h ui/controls/invite_link_buttons.cpp