From ad03431b0a447ef7366ffa164dc01749f1455e2b Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 11 Jan 2024 10:31:09 +0400 Subject: [PATCH] Improve "read time" context menu info design. --- .../SourceFiles/boxes/premium_preview_box.cpp | 195 ------------ .../SourceFiles/boxes/premium_preview_box.h | 11 - .../chat_helpers/chat_helpers.style | 7 + .../view/history_view_context_menu.cpp | 48 +-- .../info/profile/info_profile_cover.cpp | 6 +- .../ui/boxes/show_or_premium_box.cpp | 223 ++++++++++++++ .../ui/boxes/show_or_premium_box.h | 25 ++ .../controls/who_reacted_context_action.cpp | 279 +++++++++++++++++- .../ui/controls/who_reacted_context_action.h | 5 + Telegram/cmake/td_ui.cmake | 2 + 10 files changed, 564 insertions(+), 237 deletions(-) create mode 100644 Telegram/SourceFiles/ui/boxes/show_or_premium_box.cpp create mode 100644 Telegram/SourceFiles/ui/boxes/show_or_premium_box.h diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index 4f5afd8f0..06835d603 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -55,7 +55,6 @@ constexpr auto kToggleStickerTimeout = 2 * crl::time(1000); constexpr auto kStarOpacityOff = 0.1; constexpr auto kStarOpacityOn = 1.; constexpr auto kStarPeriod = 3 * crl::time(1000); -constexpr auto kShowOrLineOpacity = 0.3; using Data::ReactionId; @@ -1316,200 +1315,6 @@ void PremiumUnavailableBox(not_null box) { }); } -[[nodiscard]] object_ptr MakeShowOrPremiumIcon( - not_null parent, - not_null icon) { - const auto margin = st::showOrIconMargin; - const auto padding = st::showOrIconPadding; - const auto inner = padding.top() + icon->height() + padding.bottom(); - const auto full = margin.top() + inner + margin.bottom(); - auto result = object_ptr(parent, full); - const auto raw = result.data(); - - raw->resize(st::boxWideWidth, full); - raw->paintRequest( - ) | rpl::start_with_next([=] { - auto p = QPainter(raw); - auto hq = PainterHighQualityEnabler(p); - const auto width = raw->width(); - const auto position = QPoint((width - inner) / 2, margin.top()); - const auto rect = QRect(position, QSize(inner, inner)); - const auto shift = QPoint(padding.left(), padding.top()); - p.setPen(Qt::NoPen); - p.setBrush(st::showOrIconBg); - p.drawEllipse(rect); - icon->paint(p, position + shift, width); - }, raw->lifetime()); - - return result; -} - -[[nodiscard]] object_ptr MakeShowOrLabel( - not_null parent, - rpl::producer text) { - auto result = object_ptr( - parent, - std::move(text), - st::showOrLabel); - const auto raw = result.data(); - - raw->paintRequest( - ) | rpl::start_with_next([=] { - auto p = QPainter(raw); - - const auto full = st::showOrLineWidth; - const auto left = (raw->width() - full) / 2; - const auto text = raw->textMaxWidth() + 2 * st::showOrLabelSkip; - const auto fill = (full - text) / 2; - const auto stroke = st::lineWidth; - const auto top = st::showOrLineTop; - p.setOpacity(kShowOrLineOpacity); - p.fillRect(left, top, fill, stroke, st::windowSubTextFg); - const auto start = left + full - fill; - p.fillRect(start, top, fill, stroke, st::windowSubTextFg); - }, raw->lifetime()); - - return result; -} - -void ShowOrPremiumBox( - not_null box, - ShowOrPremium type, - QString shortName, - Fn justShow, - Fn toPremium) { - struct Skin { - rpl::producer showTitle; - rpl::producer showAbout; - rpl::producer showButton; - rpl::producer orPremium; - rpl::producer premiumTitle; - rpl::producer premiumAbout; - rpl::producer premiumButton; - QString toast; - const style::icon *icon = nullptr; - }; - auto skin = (type == ShowOrPremium::LastSeen) - ? Skin{ - tr::lng_lastseen_show_title(), - tr::lng_lastseen_show_about( - lt_user, - rpl::single(TextWithEntities{ shortName }), - Ui::Text::RichLangValue), - tr::lng_lastseen_show_button(), - tr::lng_lastseen_or(), - tr::lng_lastseen_premium_title(), - tr::lng_lastseen_premium_about( - lt_user, - rpl::single(TextWithEntities{ shortName }), - Ui::Text::RichLangValue), - tr::lng_lastseen_premium_button(), - tr::lng_lastseen_shown_toast(tr::now), - &st::showOrIconLastSeen, - } - : (type == ShowOrPremium::ReadTime) - ? Skin{ - tr::lng_readtime_show_title(), - tr::lng_readtime_show_about( - lt_user, - rpl::single(TextWithEntities{ shortName }), - Ui::Text::RichLangValue), - tr::lng_readtime_show_button(), - tr::lng_readtime_or(), - tr::lng_readtime_premium_title(), - tr::lng_readtime_premium_about( - lt_user, - rpl::single(TextWithEntities{ shortName }), - Ui::Text::RichLangValue), - tr::lng_readtime_premium_button(), - tr::lng_readtime_shown_toast(tr::now), - &st::showOrIconReadTime, - } - : Skin(); - - box->setStyle(st::showOrBox); - box->setWidth(st::boxWideWidth); - box->addTopButton(st::boxTitleClose, [=] { - box->closeBox(); - }); - - box->addRow(MakeShowOrPremiumIcon(box, skin.icon)); - box->addRow( - object_ptr( - box, - std::move(skin.showTitle), - st::boostCenteredTitle), - st::showOrTitlePadding); - box->addRow( - object_ptr( - box, - std::move(skin.showAbout), - st::boostText), - st::showOrAboutPadding); - const auto show = box->addRow( - object_ptr( - box, - std::move(skin.showButton), - st::showOrShowButton), - QMargins( - st::showOrBox.buttonPadding.left(), - 0, - st::showOrBox.buttonPadding.right(), - 0)); - show->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); - box->addRow( - MakeShowOrLabel(box, std::move(skin.orPremium)), - st::showOrLabelPadding); - box->addRow( - object_ptr( - box, - std::move(skin.premiumTitle), - st::boostCenteredTitle), - st::showOrTitlePadding); - box->addRow( - object_ptr( - box, - std::move(skin.premiumAbout), - st::boostText), - st::showOrPremiumAboutPadding); - - const auto premium = Ui::CreateChild( - box.get(), - Ui::Premium::ButtonGradientStops()); - - const auto &st = st::premiumPreviewBox.button; - premium->resize(st::showOrShowButton.width, st::showOrShowButton.height); - - const auto label = Ui::CreateChild( - premium, - std::move(skin.premiumButton), - st::premiumPreviewButtonLabel); - label->setAttribute(Qt::WA_TransparentForMouseEvents); - rpl::combine( - premium->widthValue(), - label->widthValue() - ) | rpl::start_with_next([=](int outer, int width) { - label->moveToLeft( - (outer - width) / 2, - st::premiumPreviewBox.button.textTop, - outer); - }, label->lifetime()); - - box->setShowFinishedCallback([=] { - premium->startGlareAnimation(); - }); - - box->addButton( - object_ptr::fromRaw(premium)); - - show->setClickedCallback([box, justShow, toast = skin.toast] { - justShow(); - box->uiShow()->showToast(toast); - box->closeBox(); - }); - premium->setClickedCallback(std::move(toPremium)); -} - void DoubledLimitsPreviewBox( not_null box, not_null session) { diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.h b/Telegram/SourceFiles/boxes/premium_preview_box.h index f49cdf618..60c5699ca 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.h +++ b/Telegram/SourceFiles/boxes/premium_preview_box.h @@ -83,17 +83,6 @@ void ShowPremiumPreviewToBuy( void PremiumUnavailableBox(not_null box); -enum class ShowOrPremium : uchar { - LastSeen, - ReadTime, -}; -void ShowOrPremiumBox( - not_null box, - ShowOrPremium type, - QString shortName, - Fn justShow, - Fn toPremium); - [[nodiscard]] object_ptr CreateUnlockButton( QWidget *parent, rpl::producer text); diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 99f6f9900..454c58d0e 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -249,6 +249,13 @@ defaultWhoRead: WhoRead { iconPosition: point(15px, 7px); itemPadding: margins(44px, 9px, 17px, 7px); } +whenReadStyle: TextStyle(defaultTextStyle) { + font: font(12px); +} +whenReadPadding: margins(34px, 3px, 17px, 4px); +whenReadIconPosition: point(8px, 0px); +whenReadSkip: 3px; +whenReadShowPadding: margins(6px, 0px, 6px, 2px); switchPmButton: RoundButton(defaultBoxButton) { width: 320px; diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 7cb610211..52170fa51 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -40,9 +40,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "menu/menu_item_download_files.h" #include "menu/menu_send.h" #include "ui/boxes/confirm_box.h" +#include "ui/boxes/show_or_premium_box.h" #include "boxes/delete_messages_box.h" #include "boxes/report_messages_box.h" -#include "boxes/premium_preview_box.h" #include "boxes/sticker_set_box.h" #include "boxes/stickers_box.h" #include "boxes/translate_box.h" @@ -1269,22 +1269,25 @@ void AddWhoReactedAction( const auto whoReadIds = std::make_shared(); const auto weak = Ui::MakeWeak(menu.get()); const auto user = item->history()->peer; + const auto showOrPremium = [=] { + if (const auto strong = weak.data()) { + strong->hideMenu(); + } + const auto type = Ui::ShowOrPremium::ReadTime; + const auto name = user->shortName(); + auto box = Box(Ui::ShowOrPremiumBox, type, name, [=] { + const auto api = &controller->session().api(); + api->globalPrivacy().updateHideReadTime({}); + }, [=] { + Settings::ShowPremium(controller, u"revtime_hidden"_q); + }); + controller->show(std::move(box)); + }; const auto participantChosen = [=](uint64 id) { if (const auto strong = weak.data()) { strong->hideMenu(); } - if (id) { - controller->showPeerInfo(PeerId(id)); - } else { - const auto type = ShowOrPremium::ReadTime; - auto box = Box(ShowOrPremiumBox, type, user->shortName(), [=] { - const auto api = &controller->session().api(); - api->globalPrivacy().updateHideReadTime({}); - }, [=] { - Settings::ShowPremium(controller, u"revtime_hidden"_q); - }); - controller->show(std::move(box)); - } + controller->showPeerInfo(PeerId(id)); }; const auto showAllChosen = [=, itemId = item->fullId()]{ // Pressing on an item that has a submenu doesn't hide it :( @@ -1302,12 +1305,19 @@ void AddWhoReactedAction( if (!menu->empty()) { menu->addSeparator(&st::expandedMenuSeparator); } - menu->addAction(Ui::WhoReactedContextAction( - menu.get(), - Api::WhoReacted(item, context, st::defaultWhoRead, whoReadIds), - Data::ReactedMenuFactory(&controller->session()), - participantChosen, - showAllChosen)); + if (item->history()->peer->isUser()) { + menu->addAction(Ui::WhenReadContextAction( + menu.get(), + Api::WhoReacted(item, context, st::defaultWhoRead, whoReadIds), + showOrPremium)); + } else { + menu->addAction(Ui::WhoReactedContextAction( + menu.get(), + Api::WhoReacted(item, context, st::defaultWhoRead, whoReadIds), + Data::ReactedMenuFactory(&controller->session()), + participantChosen, + showAllChosen)); + } } void ShowTagMenu( diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index 15ec3df2b..f430c03d7 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -24,9 +24,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/profile/info_profile_emoji_status_panel.h" #include "info/info_controller.h" #include "boxes/peers/edit_forum_topic_box.h" -#include "boxes/premium_preview_box.h" #include "history/view/media/history_view_sticker_player.h" #include "lang/lang_keys.h" +#include "ui/boxes/show_or_premium_box.h" #include "ui/controls/userpic_button.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" @@ -414,8 +414,8 @@ void Cover::setupShowLastSeen() { } _showLastSeen->setClickedCallback([=] { - const auto type = ShowOrPremium::LastSeen; - auto box = Box(ShowOrPremiumBox, type, user->shortName(), [=] { + const auto type = Ui::ShowOrPremium::LastSeen; + auto box = Box(Ui::ShowOrPremiumBox, type, user->shortName(), [=] { _controller->session().api().userPrivacy().save( ::Api::UserPrivacy::Key::LastSeen, {}); diff --git a/Telegram/SourceFiles/ui/boxes/show_or_premium_box.cpp b/Telegram/SourceFiles/ui/boxes/show_or_premium_box.cpp new file mode 100644 index 000000000..0e210cb72 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/show_or_premium_box.cpp @@ -0,0 +1,223 @@ +/* +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/boxes/show_or_premium_box.h" + +#include "base/object_ptr.h" +#include "lang/lang_keys.h" +#include "ui/effects/premium_graphics.h" +#include "ui/layers/generic_box.h" +#include "ui/text/text_utilities.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/gradient_round_button.h" +#include "ui/widgets/labels.h" +#include "ui/painter.h" +#include "styles/style_layers.h" +#include "styles/style_premium.h" + +namespace Ui { +namespace { + +constexpr auto kShowOrLineOpacity = 0.3; + +[[nodiscard]] object_ptr MakeShowOrPremiumIcon( + not_null parent, + not_null icon) { + const auto margin = st::showOrIconMargin; + const auto padding = st::showOrIconPadding; + const auto inner = padding.top() + icon->height() + padding.bottom(); + const auto full = margin.top() + inner + margin.bottom(); + auto result = object_ptr(parent, full); + const auto raw = result.data(); + + raw->resize(st::boxWideWidth, full); + raw->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(raw); + auto hq = PainterHighQualityEnabler(p); + const auto width = raw->width(); + const auto position = QPoint((width - inner) / 2, margin.top()); + const auto rect = QRect(position, QSize(inner, inner)); + const auto shift = QPoint(padding.left(), padding.top()); + p.setPen(Qt::NoPen); + p.setBrush(st::showOrIconBg); + p.drawEllipse(rect); + icon->paint(p, position + shift, width); + }, raw->lifetime()); + + return result; +} + +[[nodiscard]] object_ptr MakeShowOrLabel( + not_null parent, + rpl::producer text) { + auto result = object_ptr( + parent, + std::move(text), + st::showOrLabel); + const auto raw = result.data(); + + raw->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(raw); + + const auto full = st::showOrLineWidth; + const auto left = (raw->width() - full) / 2; + const auto text = raw->textMaxWidth() + 2 * st::showOrLabelSkip; + const auto fill = (full - text) / 2; + const auto stroke = st::lineWidth; + const auto top = st::showOrLineTop; + p.setOpacity(kShowOrLineOpacity); + p.fillRect(left, top, fill, stroke, st::windowSubTextFg); + const auto start = left + full - fill; + p.fillRect(start, top, fill, stroke, st::windowSubTextFg); + }, raw->lifetime()); + + return result; +} + +} // namespace + +void ShowOrPremiumBox( + not_null box, + ShowOrPremium type, + QString shortName, + Fn justShow, + Fn toPremium) { + struct Skin { + rpl::producer showTitle; + rpl::producer showAbout; + rpl::producer showButton; + rpl::producer orPremium; + rpl::producer premiumTitle; + rpl::producer premiumAbout; + rpl::producer premiumButton; + QString toast; + const style::icon *icon = nullptr; + }; + auto skin = (type == ShowOrPremium::LastSeen) + ? Skin{ + tr::lng_lastseen_show_title(), + tr::lng_lastseen_show_about( + lt_user, + rpl::single(TextWithEntities{ shortName }), + Text::RichLangValue), + tr::lng_lastseen_show_button(), + tr::lng_lastseen_or(), + tr::lng_lastseen_premium_title(), + tr::lng_lastseen_premium_about( + lt_user, + rpl::single(TextWithEntities{ shortName }), + Text::RichLangValue), + tr::lng_lastseen_premium_button(), + tr::lng_lastseen_shown_toast(tr::now), + &st::showOrIconLastSeen, + } + : (type == ShowOrPremium::ReadTime) + ? Skin{ + tr::lng_readtime_show_title(), + tr::lng_readtime_show_about( + lt_user, + rpl::single(TextWithEntities{ shortName }), + Text::RichLangValue), + tr::lng_readtime_show_button(), + tr::lng_readtime_or(), + tr::lng_readtime_premium_title(), + tr::lng_readtime_premium_about( + lt_user, + rpl::single(TextWithEntities{ shortName }), + Text::RichLangValue), + tr::lng_readtime_premium_button(), + tr::lng_readtime_shown_toast(tr::now), + &st::showOrIconReadTime, + } + : Skin(); + + box->setStyle(st::showOrBox); + box->setWidth(st::boxWideWidth); + box->addTopButton(st::boxTitleClose, [=] { + box->closeBox(); + }); + + box->addRow(MakeShowOrPremiumIcon(box, skin.icon)); + box->addRow( + object_ptr( + box, + std::move(skin.showTitle), + st::boostCenteredTitle), + st::showOrTitlePadding); + box->addRow( + object_ptr( + box, + std::move(skin.showAbout), + st::boostText), + st::showOrAboutPadding); + const auto show = box->addRow( + object_ptr( + box, + std::move(skin.showButton), + st::showOrShowButton), + QMargins( + st::showOrBox.buttonPadding.left(), + 0, + st::showOrBox.buttonPadding.right(), + 0)); + show->setTextTransform(RoundButton::TextTransform::NoTransform); + box->addRow( + MakeShowOrLabel(box, std::move(skin.orPremium)), + st::showOrLabelPadding); + box->addRow( + object_ptr( + box, + std::move(skin.premiumTitle), + st::boostCenteredTitle), + st::showOrTitlePadding); + box->addRow( + object_ptr( + box, + std::move(skin.premiumAbout), + st::boostText), + st::showOrPremiumAboutPadding); + + const auto premium = CreateChild( + box.get(), + Premium::ButtonGradientStops()); + + const auto &st = st::premiumPreviewBox.button; + premium->resize(st::showOrShowButton.width, st::showOrShowButton.height); + + const auto label = CreateChild( + premium, + std::move(skin.premiumButton), + st::premiumPreviewButtonLabel); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + rpl::combine( + premium->widthValue(), + label->widthValue() + ) | rpl::start_with_next([=](int outer, int width) { + label->moveToLeft( + (outer - width) / 2, + st::premiumPreviewBox.button.textTop, + outer); + }, label->lifetime()); + + box->setShowFinishedCallback([=] { + premium->startGlareAnimation(); + }); + + box->addButton( + object_ptr::fromRaw(premium)); + + show->setClickedCallback([box, justShow, toast = skin.toast] { + justShow(); + box->uiShow()->showToast(toast); + box->closeBox(); + }); + premium->setClickedCallback(std::move(toPremium)); +} + +} // namespace Ui \ No newline at end of file diff --git a/Telegram/SourceFiles/ui/boxes/show_or_premium_box.h b/Telegram/SourceFiles/ui/boxes/show_or_premium_box.h new file mode 100644 index 000000000..4c0a10104 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/show_or_premium_box.h @@ -0,0 +1,25 @@ +/* +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 + +namespace Ui { + +class GenericBox; + +enum class ShowOrPremium : uchar { + LastSeen, + ReadTime, +}; +void ShowOrPremiumBox( + not_null box, + ShowOrPremium type, + QString shortName, + Fn justShow, + Fn toPremium); + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp index b6d24e3b8..f9c3b0458 100644 --- a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp +++ b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp @@ -124,6 +124,45 @@ private: }; +class WhenAction final : public Menu::ItemBase { +public: + WhenAction( + not_null parentMenu, + rpl::producer content, + Fn showOrPremium); + + bool isEnabled() const override; + not_null action() const override; + +protected: + QPoint prepareRippleStartPosition() const override; + QImage prepareRippleMask() const override; + + int contentHeight() const override; + +private: + void paint(Painter &p); + void resizeEvent(QResizeEvent *e) override; + + void resolveMinWidth(); + void refreshText(); + void refreshDimensions(); + + const not_null _parentMenu; + const not_null _dummyAction; + const Fn _showOrPremium; + const style::Menu &_st; + + Text::String _text; + Text::String _show; + QRect _showRect; + int _textWidth = 0; + const int _height = 0; + + WhoReadContent _content; + +}; + TextParseOptions MenuTextOptions = { TextParseLinks, // flags 0, // maxw @@ -219,10 +258,6 @@ Action::Action( if (const auto onstack = _showAllChosen) { onstack(); } - } else if (_content.state == WhoReadState::MyHidden) { - if (const auto onstack = _participantChosen) { - onstack(0); - } } }, lifetime()); @@ -385,11 +420,6 @@ void Action::refreshText() { _st.itemStyle, { ((_content.state == WhoReadState::Unknown) ? tr::lng_context_seen_loading(tr::now) - : (_content.state == WhoReadState::MyHidden) - ? tr::lng_context_read_show(tr::now) - : (_content.state == WhoReadState::HisHidden - || _content.state == WhoReadState::TooOld) - ? tr::lng_context_read_hidden(tr::now) : (usersCount == 1) ? _content.participants.front().name : (_content.fullReactionsCount > 0 @@ -470,6 +500,227 @@ void Action::handleKeyPress(not_null e) { } } +WhenAction::WhenAction( + not_null parentMenu, + rpl::producer content, + Fn showOrPremium) +: ItemBase(parentMenu->menu(), parentMenu->menu()->st()) +, _parentMenu(parentMenu) +, _dummyAction(CreateChild(parentMenu->menu().get())) +, _showOrPremium(std::move(showOrPremium)) +, _st(parentMenu->menu()->st()) +, _height(st::whenReadPadding.top() + + st::whenReadStyle.font->height + + st::whenReadPadding.bottom()) { + const auto parent = parentMenu->menu(); + + setAcceptBoth(true); + initResizeHook(parent->sizeValue()); + + std::move( + content + ) | rpl::start_with_next([=](WhoReadContent &&content) { + const auto changed = (_content.participants != content.participants) + || (_content.state != content.state); + _content = content; + refreshText(); + refreshDimensions(); + setPointerCursor(isEnabled()); + _dummyAction->setEnabled(isEnabled()); + if (!isEnabled()) { + setSelected(false); + } + update(); + }, lifetime()); + + resolveMinWidth(); + refreshDimensions(); + + paintRequest( + ) | rpl::start_with_next([=] { + Painter p(this); + paint(p); + }, lifetime()); + + clicks( + ) | rpl::start_with_next([=] { + if (_content.state == WhoReadState::MyHidden) { + if (const auto onstack = _showOrPremium) { + onstack(); + } + } + }, lifetime()); + + enableMouseSelecting(); +} + +void WhenAction::resolveMinWidth() { + const auto width = [&](const QString &text) { + return st::whenReadStyle.font->width(text); + }; + const auto added = st::whenReadShowPadding.left() + + st::whenReadShowPadding.right(); + + const auto sampleDate = QDate::currentDate(); + const auto sampleTime = QLocale().toString( + QTime::currentTime(), + QLocale::ShortFormat); + const auto maxTextWidth = added + std::max({ + width(tr::lng_contacts_loading(tr::now)), + (width(tr::lng_context_read_hidden(tr::now)) + + st::whenReadSkip + + width(tr::lng_context_read_show(tr::now))), + width(tr::lng_mediaview_today(tr::now, lt_time, sampleTime)), + width(tr::lng_mediaview_yesterday(tr::now, lt_time, sampleTime)), + width(tr::lng_mediaview_date_time( + tr::now, + lt_date, + tr::lng_month_day( + tr::now, + lt_month, + Lang::MonthDay(sampleDate.month())(tr::now), + lt_day, + QString::number(sampleDate.day())), + lt_time, + sampleTime)), + }); + + const auto maxWidth = st::whenReadPadding.left() + + maxTextWidth + + st::whenReadPadding.right(); + setMinWidth(maxWidth); +} + +void WhenAction::paint(Painter &p) { + const auto loading = !isEnabled() && _content.participants.empty(); + 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, _st.itemBg); + const auto &icon = loading + ? st::whoReadChecksDisabled + : selected + ? st::whoReadChecksOver + : st::whoReadChecks; + icon.paint(p, st::whenReadIconPosition, width()); + p.setPen(loading ? _st.itemFgDisabled : _st.itemFg); + _text.drawLeftElided( + p, + st::whenReadPadding.left(), + st::whenReadPadding.top(), + _textWidth, + width()); + if (!_show.isEmpty()) { + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(_st.itemBgOver); + const auto radius = _showRect.height() / 2.; + p.drawRoundedRect(_showRect, radius, radius); + paintRipple(p, 0, 0); + const auto inner = _showRect.marginsRemoved(st::whenReadShowPadding); + p.setPen(_st.itemFgOver); + _show.drawLeftElided( + p, + inner.x(), + inner.y(), + inner.width(), + width()); + } +} + +void WhenAction::refreshText() { + const auto usersCount = int(_content.participants.size()); + const auto onlySeenCount = ranges::count( + _content.participants, + QString(), + &WhoReadParticipant::customEntityData); + const auto count = std::max(_content.fullReactionsCount, usersCount); + _text.setMarkedText( + st::whenReadStyle, + { ((_content.state == WhoReadState::Unknown) + ? tr::lng_context_seen_loading(tr::now) + : _content.participants.empty() + ? tr::lng_context_read_hidden(tr::now) + : _content.participants.front().date) }, + MenuTextOptions); + if (_content.state == WhoReadState::MyHidden) { + _show.setMarkedText( + st::whenReadStyle, + { tr::lng_context_read_show(tr::now) }, + MenuTextOptions); + } else { + _show = Text::String(); + } +} + +void WhenAction::resizeEvent(QResizeEvent *e) { + ItemBase::resizeEvent(e); + refreshDimensions(); +} + +void WhenAction::refreshDimensions() { + if (!minWidth()) { + return; + } + const auto textWidth = _text.maxWidth(); + const auto showWidth = _show.isEmpty() ? 0 : _show.maxWidth(); + const auto &padding = st::whenReadPadding; + + const auto goodWidth = padding.left() + + textWidth + + (showWidth + ? (st::whenReadSkip + + st::whenReadShowPadding.left() + + showWidth + + st::whenReadShowPadding.right()) + : 0) + + padding.right(); + + const auto w = std::clamp( + goodWidth, + _st.widthMin, + std::max(width(), _st.widthMin)); + _textWidth = std::min(w - (goodWidth - textWidth), textWidth); + if (showWidth) { + _showRect = QRect( + padding.left() + _textWidth + st::whenReadSkip, + padding.top() - st::whenReadShowPadding.top(), + (st::whenReadShowPadding.left() + + showWidth + + st::whenReadShowPadding.right()), + (st::whenReadShowPadding.top() + + st::whenReadStyle.font->height + + st::whenReadShowPadding.bottom())); + } +} + +bool WhenAction::isEnabled() const { + return (_content.state == WhoReadState::MyHidden); +} + +not_null WhenAction::action() const { + return _dummyAction; +} + +QPoint WhenAction::prepareRippleStartPosition() const { + const auto result = mapFromGlobal(QCursor::pos()); + return _showRect.contains(result) + ? result + : Ui::RippleButton::DisabledRippleStartPosition(); +} + +QImage WhenAction::prepareRippleMask() const { + return Ui::RippleAnimation::MaskByDrawer(size(), false, [&](QPainter &p) { + const auto radius = _showRect.height() / 2.; + p.drawRoundedRect(_showRect, radius, radius); + }); +} + +int WhenAction::contentHeight() const { + return _height; +} + } // namespace WhoReactedEntryAction::WhoReactedEntryAction( @@ -676,6 +927,16 @@ base::unique_qptr WhoReactedContextAction( std::move(showAllChosen)); } +base::unique_qptr WhenReadContextAction( + not_null menu, + rpl::producer content, + Fn showOrPremium) { + return base::make_unique_q( + menu, + std::move(content), + std::move(showOrPremium)); +} + WhoReactedListMenu::WhoReactedListMenu( CustomEmojiFactory factory, Fn participantChosen, diff --git a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h index 77a54a4e2..1f35a3216 100644 --- a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h +++ b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h @@ -62,6 +62,11 @@ struct WhoReadContent { Fn participantChosen, Fn showAllChosen); +[[nodiscard]] base::unique_qptr WhenReadContextAction( + not_null menu, + rpl::producer content, + Fn showOrPremium); + enum class WhoReactedType : uchar { Viewed, Reacted, diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 4df7b5c6e..53db41bc6 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -235,6 +235,8 @@ PRIVATE ui/boxes/rate_call_box.h ui/boxes/report_box.cpp ui/boxes/report_box.h + ui/boxes/show_or_premium_box.cpp + ui/boxes/show_or_premium_box.h ui/boxes/single_choice_box.cpp ui/boxes/single_choice_box.h ui/boxes/time_picker_box.cpp