diff --git a/Telegram/Resources/icons/settings/premium/large_lastseen.png b/Telegram/Resources/icons/settings/premium/large_lastseen.png new file mode 100644 index 000000000..e47bd7935 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_lastseen.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_lastseen@2x.png b/Telegram/Resources/icons/settings/premium/large_lastseen@2x.png new file mode 100644 index 000000000..305b6a491 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_lastseen@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_lastseen@3x.png b/Telegram/Resources/icons/settings/premium/large_lastseen@3x.png new file mode 100644 index 000000000..0cd1e12ab Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_lastseen@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_readtime.png b/Telegram/Resources/icons/settings/premium/large_readtime.png new file mode 100644 index 000000000..c06294a82 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_readtime.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_readtime@2x.png b/Telegram/Resources/icons/settings/premium/large_readtime@2x.png new file mode 100644 index 000000000..f30a355ee Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_readtime@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_readtime@3x.png b/Telegram/Resources/icons/settings/premium/large_readtime@3x.png new file mode 100644 index 000000000..1e7d022a3 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_readtime@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index bf186e1f7..73d0fde25 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -170,21 +170,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_remember" = "Remember this choice"; -"lng_lastseen_show_title" = "Show your last seen"; -"lng_lastseen_show_about" = "To see **{user}'s** Last Seen time, either start showing your own Last Seen time..."; -"lng_lastseen_show_button" = "Show my Last Seen"; +"lng_lastseen_show_title" = "Show Your Last Seen"; +"lng_lastseen_show_about" = "To see **{user}'s** Last Seen time, either start\nshowing your own Last Seen time..."; +"lng_lastseen_show_button" = "Show My Last Seen"; "lng_lastseen_or" = "or"; "lng_lastseen_premium_title" = "Upgrade to Premium"; -"lng_lastseen_premium_about" = "Subscription will let you see **{user}'s** Last Seen status without showing yours."; +"lng_lastseen_premium_about" = "Subscription will let you see **{user}'s** Last Seen\nstatus without showing yours."; "lng_lastseen_premium_button" = "Subscribe to Telegram Premium"; "lng_lastseen_shown_toast" = "Your last seen time is now visible."; -"lng_readtime_show_title" = "Show your read date"; -"lng_readtime_show_about" = "To see when **{user}** read the message, either start showing your own read time..."; -"lng_readtime_show_button" = "Show my Read Time"; +"lng_readtime_show_title" = "Show Your Read Date"; +"lng_readtime_show_about" = "To see when **{user}** read the message,\neither start showing your own read time..."; +"lng_readtime_show_button" = "Show My Read Time"; "lng_readtime_or" = "or"; "lng_readtime_premium_title" = "Upgrade to Premium"; -"lng_readtime_premium_about" = "Subscription will let you see **{user}'s** read time without showing yours."; +"lng_readtime_premium_about" = "Subscription will let you see **{user}'s** read time\nwithout showing yours."; "lng_readtime_premium_button" = "Subscribe to Telegram Premium"; "lng_readtime_shown_toast" = "Your read times are now visible."; diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index 06835d603..4f5afd8f0 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -55,6 +55,7 @@ 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; @@ -1315,6 +1316,200 @@ void PremiumUnavailableBox(not_null<Ui::GenericBox*> box) { }); } +[[nodiscard]] object_ptr<Ui::RpWidget> MakeShowOrPremiumIcon( + not_null<Ui::RpWidget*> parent, + not_null<const style::icon*> 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<Ui::FixedHeightWidget>(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<Ui::RpWidget> MakeShowOrLabel( + not_null<Ui::RpWidget*> parent, + rpl::producer<QString> text) { + auto result = object_ptr<Ui::FlatLabel>( + 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<Ui::GenericBox*> box, + ShowOrPremium type, + QString shortName, + Fn<void()> justShow, + Fn<void()> toPremium) { + struct Skin { + rpl::producer<QString> showTitle; + rpl::producer<TextWithEntities> showAbout; + rpl::producer<QString> showButton; + rpl::producer<QString> orPremium; + rpl::producer<QString> premiumTitle; + rpl::producer<TextWithEntities> premiumAbout; + rpl::producer<QString> 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<Ui::FlatLabel>( + box, + std::move(skin.showTitle), + st::boostCenteredTitle), + st::showOrTitlePadding); + box->addRow( + object_ptr<Ui::FlatLabel>( + box, + std::move(skin.showAbout), + st::boostText), + st::showOrAboutPadding); + const auto show = box->addRow( + object_ptr<Ui::RoundButton>( + 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<Ui::FlatLabel>( + box, + std::move(skin.premiumTitle), + st::boostCenteredTitle), + st::showOrTitlePadding); + box->addRow( + object_ptr<Ui::FlatLabel>( + box, + std::move(skin.premiumAbout), + st::boostText), + st::showOrPremiumAboutPadding); + + const auto premium = Ui::CreateChild<Ui::GradientButton>( + box.get(), + Ui::Premium::ButtonGradientStops()); + + const auto &st = st::premiumPreviewBox.button; + premium->resize(st::showOrShowButton.width, st::showOrShowButton.height); + + const auto label = Ui::CreateChild<Ui::FlatLabel>( + 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<Ui::AbstractButton>::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<Ui::GenericBox*> box, not_null<Main::Session*> session) { diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.h b/Telegram/SourceFiles/boxes/premium_preview_box.h index 60c5699ca..f49cdf618 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.h +++ b/Telegram/SourceFiles/boxes/premium_preview_box.h @@ -83,6 +83,17 @@ void ShowPremiumPreviewToBuy( void PremiumUnavailableBox(not_null<Ui::GenericBox*> box); +enum class ShowOrPremium : uchar { + LastSeen, + ReadTime, +}; +void ShowOrPremiumBox( + not_null<Ui::GenericBox*> box, + ShowOrPremium type, + QString shortName, + Fn<void()> justShow, + Fn<void()> toPremium); + [[nodiscard]] object_ptr<Ui::GradientButton> CreateUnlockButton( QWidget *parent, rpl::producer<QString> text); diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index 3f9445787..15ec3df2b 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_cover.h" +#include "api/api_user_privacy.h" #include "data/data_peer_values.h" #include "data/data_channel.h" #include "data/data_chat.h" @@ -23,6 +24,7 @@ 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/controls/userpic_button.h" @@ -373,23 +375,54 @@ void Cover::setupShowLastSeen() { && !user->isBot() && !user->isServiceUser() && user->session().premiumPossible()) { + if (user->session().premium()) { + if (user->onlineTill == kOnlineHidden) { + user->updateFullForced(); + } + _showLastSeen->hide(); + return; + } + rpl::combine( user->session().changes().peerFlagsValue( user, Data::PeerUpdate::Flag::OnlineStatus), Data::AmPremiumValue(&user->session()) - ) | rpl::start_with_next([=] { - _showLastSeen->setVisible( - (user->onlineTill == kOnlineHidden) - && !user->session().premium() - && user->session().premiumPossible()); + ) | rpl::start_with_next([=](auto, bool premium) { + const auto wasShown = !_showLastSeen->isHidden(); + const auto onlineHidden = (user->onlineTill == kOnlineHidden); + const auto shown = onlineHidden + && !premium + && user->session().premiumPossible(); + _showLastSeen->setVisible(shown); + if (wasShown && premium && onlineHidden) { + user->updateFullForced(); + } + }, _showLastSeen->lifetime()); + + _controller->session().api().userPrivacy().value( + Api::UserPrivacy::Key::LastSeen + ) | rpl::filter([=](Api::UserPrivacy::Rule rule) { + return (rule.option == Api::UserPrivacy::Option::Everyone); + }) | rpl::start_with_next([=] { + if (user->onlineTill == kOnlineHidden) { + user->updateFullForced(); + } }, _showLastSeen->lifetime()); } else { _showLastSeen->hide(); } _showLastSeen->setClickedCallback([=] { - ::Settings::ShowPremium(_controller, u"lastseen_hidden"_q); + const auto type = ShowOrPremium::LastSeen; + auto box = Box(ShowOrPremiumBox, type, user->shortName(), [=] { + _controller->session().api().userPrivacy().save( + ::Api::UserPrivacy::Key::LastSeen, + {}); + }, [=] { + ::Settings::ShowPremium(_controller, u"lastseen_hidden"_q); + }); + _controller->show(std::move(box)); }); } diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index c38afcd32..34e98bd42 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -710,21 +710,13 @@ object_ptr<Ui::RpWidget> LastSeenPrivacyController::setupBelowWidget( void LastSeenPrivacyController::confirmSave( bool someAreDisallowed, Fn<void()> saveCallback) { - const auto privacy = &_session->api().globalPrivacy(); - const auto hideReadTime = _hideReadTime; - const auto save = [=, saveCallback = std::move(saveCallback)] { - if (privacy->hideReadTimeCurrent() != hideReadTime) { - privacy->updateHideReadTime(hideReadTime); - } - saveCallback(); - }; if (someAreDisallowed && !Core::App().settings().lastSeenWarningSeen()) { auto callback = [ =, - save = std::move(save) + saveCallback = std::move(saveCallback) ](Fn<void()> &&close) { close(); - save(); + saveCallback(); Core::App().settings().setLastSeenWarningSeen(true); Core::App().saveSettingsDelayed(); }; @@ -735,7 +727,14 @@ void LastSeenPrivacyController::confirmSave( }); Ui::show(std::move(box), Ui::LayerOption::KeepOther); } else { - save(); + saveCallback(); + } +} + +void LastSeenPrivacyController::saveAdditional() { + const auto privacy = &_session->api().globalPrivacy(); + if (privacy->hideReadTimeCurrent() != _hideReadTime) { + privacy->updateHideReadTime(_hideReadTime); } } diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.h b/Telegram/SourceFiles/settings/settings_privacy_controllers.h index 5211d0731..7b097ec44 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.h +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.h @@ -117,6 +117,8 @@ public: bool someAreDisallowed, Fn<void()> saveCallback) override; + void saveAdditional() override; + private: const not_null<::Main::Session*> _session; bool _hideReadTime = false; diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index c9c9ccd84..5b97d8cb4 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -303,3 +303,29 @@ boostReplaceIconSkip: 3px; boostReplaceIconOutline: 2px; boostReplaceIconAdd: point(4px, 2px); boostReplaceArrow: icon{{ "mediaview/next", windowSubTextFg }}; + +showOrIconLastSeen: icon{{ "settings/premium/large_lastseen", windowFgActive }}; +showOrIconReadTime: icon{{ "settings/premium/large_readtime", windowFgActive }}; +showOrIconBg: windowBgActive; +showOrIconPadding: margins(12px, 12px, 12px, 12px); +showOrIconMargin: margins(0px, 28px, 0px, 12px); +showOrTitlePadding: margins(0px, 0px, 0px, 5px); +showOrAboutPadding: margins(0px, 0px, 0px, 16px); +showOrShowButton: RoundButton(defaultActiveButton) { + width: 308px; + height: 42px; + textTop: 12px; + font: font(13px semibold); +} +showOrLabel: FlatLabel(boostText) { + textFg: windowSubTextFg; +} +showOrLineWidth: 190px; +showOrLabelSkip: 7px; +showOrLineTop: 10px; +showOrLabelPadding: margins(0px, 17px, 0px, 13px); +showOrPremiumAboutPadding: margins(0px, 0px, 0px, 0px); +showOrBox: Box(boostBox) { + buttonPadding: margins(28px, 16px, 28px, 27px); + button: showOrShowButton; +}