From 667822ca08f97ba97ed91eb4e5683d9f6bf2a7b3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 1 Aug 2025 20:53:59 +0400 Subject: [PATCH] Improve negative rating display. --- .../Resources/icons/levels/level_warning.svg | 7 ++ Telegram/Resources/langs/lang.strings | 4 + .../SourceFiles/data/data_peer_values.cpp | 6 +- .../info/profile/info_levels.style | 4 + .../SourceFiles/ui/controls/stars_rating.cpp | 80 +++++++++++++++---- Telegram/SourceFiles/ui/effects/premium.style | 10 +++ .../SourceFiles/ui/effects/premium_bubble.cpp | 10 +-- .../SourceFiles/ui/effects/premium_bubble.h | 1 + .../ui/effects/premium_graphics.cpp | 13 +-- 9 files changed, 110 insertions(+), 25 deletions(-) create mode 100644 Telegram/Resources/icons/levels/level_warning.svg diff --git a/Telegram/Resources/icons/levels/level_warning.svg b/Telegram/Resources/icons/levels/level_warning.svg new file mode 100644 index 0000000000..cba1db0076 --- /dev/null +++ b/Telegram/Resources/icons/levels/level_warning.svg @@ -0,0 +1,7 @@ + + + Icon / Filled / Folder / filled_warning + + + + \ No newline at end of file diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 641f0b66d7..f7f2fef5a5 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1882,6 +1882,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stars_rating_pending#other" = "The rating updates in 21 days after purchases.\n{count} points are pending. {link}"; "lng_stars_rating_pending_preview" = "Preview {arrow}"; "lng_stars_rating_pending_back" = "Back {arrow}"; +"lng_stars_rating_negative_label" = "Negative Rating"; +"lng_stars_rating_negative" = "A negative rating indicates that **{name}'s** payments are unreliable."; +"lng_stars_rating_negative_your#one" = "A negative rating indicates that your payments are unreliable. Spend **{count} Star** to fix this issue."; +"lng_stars_rating_negative_your#other" = "A negative rating indicates that your payments are unreliable. Spend **{count} Stars** to fix this issue."; "lng_stars_rating_about" = "This rating reflects **{name}'s** activity on Telegram. What affects it:"; "lng_stars_rating_about_your" = "This rating reflects your activity on Telegram. What affects it:"; "lng_stars_title_gifts_telegram" = "Gifts from Telegram"; diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index 0da1a8137f..77fdb049b3 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -525,7 +525,11 @@ rpl::producer StarsRatingValue( user, Data::PeerUpdate::Flag::StarsRating ) | rpl::map([=] { - return user->starsRating(); + auto result = user->starsRating(); + if (!user->isSelf() && result.level < 0) { + result.stars = 0; + } + return result; }); } return rpl::single(Data::StarsRating()); diff --git a/Telegram/SourceFiles/info/profile/info_levels.style b/Telegram/SourceFiles/info/profile/info_levels.style index e1d38d51a8..9d1ae14eac 100644 --- a/Telegram/SourceFiles/info/profile/info_levels.style +++ b/Telegram/SourceFiles/info/profile/info_levels.style @@ -74,3 +74,7 @@ level80: LevelShape(levelBase) { level90: LevelShape(levelBase) { icon: icon {{ "levels/level90_inner-31x31", windowBgActive }}; } +levelNegative: LevelShape(levelBase) { + icon: icon {{ "levels/level_warning-18x18", attentionButtonFg, point(6px, 7px) }}; +} +levelNegativeBubble: icon {{ "levels/level_warning-28x28", windowFgActive }}; diff --git a/Telegram/SourceFiles/ui/controls/stars_rating.cpp b/Telegram/SourceFiles/ui/controls/stars_rating.cpp index 0adfa4e500..6983bb64f4 100644 --- a/Telegram/SourceFiles/ui/controls/stars_rating.cpp +++ b/Telegram/SourceFiles/ui/controls/stars_rating.cpp @@ -157,13 +157,19 @@ void FillRatingLimit( rpl::producer<> showFinished, not_null container, rpl::producer data, + Premium::BubbleType type, style::margins limitLinePadding, - int starsForScale) { + int starsForScale, + bool hideCount) { const auto addSkip = [&](int skip) { container->add(object_ptr(container, skip)); }; + const auto negative = (type == Premium::BubbleType::NegativeRating); const auto ratio = [=](Counters rating) { + if (negative) { + return 0.5; + } const auto min = rating.thisLevelStars; const auto max = rating.nextLevelStars; @@ -213,12 +219,14 @@ void FillRatingLimit( }); Premium::AddBubbleRow( container, - st::boostBubble, + (hideCount ? st::iconOnlyPremiumBubble : st::boostBubble), std::move(showFinished), rpl::duplicate(bubbleRowState), - Premium::BubbleType::StarRating, - BubbleTextFactory(starsForScale), - &st::infoStarsCrown, + type, + (hideCount + ? [](int) { return QString(); } + : BubbleTextFactory(starsForScale)), + negative ? &st::levelNegativeBubble : &st::infoStarsCrown, limitLinePadding); addSkip(st::premiumLineTextSkip); @@ -227,30 +235,35 @@ void FillRatingLimit( }; auto limitState = std::move( bubbleRowState - ) | rpl::map([](const Premium::BubbleRowState &state) { + ) | rpl::map([negative](const Premium::BubbleRowState &state) { return Premium::LimitRowState{ - .ratio = state.ratio, - .animateFromZero = state.animateFromZero, + .ratio = negative ? 0.5 : state.ratio, + .animateFromZero = !negative && state.animateFromZero, .dynamic = state.dynamic }; }); auto left = rpl::duplicate( adjustedData ) | rpl::map([=](Counters counters) { - return level(counters.level); + return (counters.level < 0) ? QString() : level(counters.level); }); auto right = rpl::duplicate( adjustedData ) | rpl::map([=](Counters counters) { - return level(counters.level + 1); + return (counters.level < 0) + ? tr::lng_stars_rating_negative_label(tr::now) + : level(counters.level + 1); }); Premium::AddLimitRow( container, - st::boostLimits, + (negative ? st::negativeStarsLimits : st::boostLimits), Premium::LimitRowLabels{ .leftLabel = std::move(left), .rightLabel = std::move(right), - .activeLineBg = [=] { return st::windowBgActive->b; }, + .activeLineBg = [=] { return negative + ? st::attentionButtonFg->b + : st::windowBgActive->b; + }, }, std::move(limitState), limitLinePadding); @@ -276,8 +289,12 @@ void AboutRatingBox( BoxShowFinishes(box), box->verticalLayout(), state->data.value(), + (data.level < 0 + ? Premium::BubbleType::NegativeRating + : Premium::BubbleType::StarRating), st::boxRowPadding, - data.stars); + data.stars, + (data.level < 0 && !data.stars)); box->setMaxHeight(st::boostBoxMaxHeight); @@ -294,10 +311,40 @@ void AboutRatingBox( : tr::lng_stars_rating_about_your( Ui::Text::RichLangValue) | rpl::type_erased(); + if (data.level < 0) { + auto text = (data.stars < 0) + ? tr::lng_stars_rating_negative_your( + lt_count_decimal, + rpl::single(-data.stars * 1.), + Ui::Text::RichLangValue) + : tr::lng_stars_rating_negative( + lt_name, + rpl::single(TextWithEntities{ name }), + Ui::Text::RichLangValue); + const auto aboutNegative = box->addRow( + object_ptr( + box, + std::move(text), + st::boostTextNegative), + (st::boxRowPadding + + QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip))); + aboutNegative->setTryMakeSimilarLines(true); + } + box->addRow( object_ptr(box, std::move(title), st::infoStarsTitle), st::boxRowPadding + QMargins(0, st::boostTitleSkip / 2, 0, 0)); + AssertIsDebug(); + pending = { + .value = { + .level = 10, + .stars = 100, + .thisLevelStars = 90, + .nextLevelStars = 110, + }, + .date = 86400, + }; if (pending) { auto text = state->pending.value( ) | rpl::map([=](bool value) { @@ -398,6 +445,9 @@ void AboutRatingBox( } [[nodiscard]] not_null SelectShape(int level) { + if (level < 0) { + return &st::levelNegative; + } struct Shape { int level = 0; not_null shape; @@ -480,7 +530,9 @@ void StarsRating::updateData(Data::StarsRating rating) { _shape = SelectShape(rating.level); _collapsedText.setText( st::levelStyle, - Lang::FormatCountDecimal(rating.level)); + (rating.level < 0 + ? QString() + : Lang::FormatCountDecimal(rating.level))); _widthValue = _shape->icon.width() - st::levelMargin.left(); } updateWidth(); diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index 75422a7514..a510a70963 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -255,6 +255,9 @@ boostLimits: PremiumLimits(defaultPremiumLimits) { gradientFromLeft: true; nonPremiumBg: windowBgRipple; } +negativeStarsLimits: PremiumLimits(boostLimits) { + gradientFromLeft: false; +} boostBubble: PremiumBubble(defaultPremiumBubble) { height: 32px; padding: margins(7px, 0px, 11px, 0px); @@ -263,6 +266,10 @@ boostBubble: PremiumBubble(defaultPremiumBubble) { tailSize: size(14px, 6px); font: font(16px); } +iconOnlyPremiumBubble: PremiumBubble(boostBubble) { + padding: margins(7px, 0px, 7px, 0px); + textSkip: 0px; +} boostTitleSkip: 32px; boostTitle: FlatLabel(defaultFlatLabel) { minWidth: 40px; @@ -289,6 +296,9 @@ boostText: FlatLabel(defaultFlatLabel) { boostTextPending: FlatLabel(boostText) { textFg: windowSubTextFg; } +boostTextNegative: FlatLabel(boostText) { + textFg: attentionButtonFg; +} boostReassignText: FlatLabel(defaultFlatLabel) { minWidth: 40px; align: align(top); diff --git a/Telegram/SourceFiles/ui/effects/premium_bubble.cpp b/Telegram/SourceFiles/ui/effects/premium_bubble.cpp index 75a6965a1d..8d3629ec70 100644 --- a/Telegram/SourceFiles/ui/effects/premium_bubble.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_bubble.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/padding_wrap.h" #include "ui/wrap/vertical_layout.h" #include "ui/painter.h" +#include "styles/style_info_levels.h" #include "styles/style_layers.h" #include "styles/style_premium.h" @@ -181,11 +182,9 @@ void Bubble::paintBubble(QPainter &p, const QRect &r, const QBrush &brush) { p.setPen(st::activeButtonFg); p.setFont(_st.font); const auto iconLeft = r.x() + _st.padding.left(); - _icon->paint( - p, - iconLeft, - bubbleRect.y() + (bubbleRect.height() - _icon->height()) / 2, - bubbleRect.width()); + const auto iconTop = bubbleRect.y() + + (bubbleRect.height() - _icon->height()) / 2; + _icon->paint(p, iconLeft, iconTop, bubbleRect.width()); _numberAnimation.paint( p, iconLeft + _icon->width() + _st.textSkip, @@ -424,6 +423,7 @@ void BubbleWidget::paintEvent(QPaintEvent *e) { switch (_type) { case BubbleType::NoPremium: case BubbleType::StarRating: return st::windowBgActive->b; + case BubbleType::NegativeRating: return st::attentionButtonFg->b; case BubbleType::Premium: return QBrush(_cachedGradient); case BubbleType::Credits: return st::creditsBg3->b; } diff --git a/Telegram/SourceFiles/ui/effects/premium_bubble.h b/Telegram/SourceFiles/ui/effects/premium_bubble.h index b867250400..4d621a47a4 100644 --- a/Telegram/SourceFiles/ui/effects/premium_bubble.h +++ b/Telegram/SourceFiles/ui/effects/premium_bubble.h @@ -89,6 +89,7 @@ struct BubbleRowState { enum class BubbleType : uchar { StarRating, + NegativeRating, NoPremium, Premium, Credits, diff --git a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp index 2ace588b1f..f4d7c4f90d 100644 --- a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp @@ -372,7 +372,6 @@ void Line::recache(const QSize &s) { } }; const auto textPadding = st::premiumLineTextSkip; - const auto textTop = (s.height() - _leftLabel.minHeight()) / 2; const auto rwidth = _rightLabel.maxWidth(); const auto pen = [&](bool gradient) { return gradient ? st::activeButtonFg : _st.nonPremiumFg; @@ -385,8 +384,10 @@ void Line::recache(const QSize &s) { if (_dynamic) { p.setFont(st::normalFont); p.setPen(pen(_st.gradientFromLeft)); - _leftLabel.drawLeft(p, textPadding, textTop, width, width); - _rightLabel.drawRight(p, textPadding, textTop, rwidth, width); + const auto leftTop = (s.height() - _leftLabel.minHeight()) / 2; + _leftLabel.drawLeft(p, textPadding, leftTop, width, width); + const auto rightTop = (s.height() - _rightLabel.minHeight()) / 2; + _rightLabel.drawRight(p, textPadding, rightTop, rwidth, width); } _leftPixmap = std::move(leftPixmap); } @@ -398,8 +399,10 @@ void Line::recache(const QSize &s) { if (_dynamic) { p.setFont(st::normalFont); p.setPen(pen(!_st.gradientFromLeft)); - _leftLabel.drawLeft(p, textPadding, textTop, width, width); - _rightLabel.drawRight(p, textPadding, textTop, rwidth, width); + const auto leftTop = (s.height() - _leftLabel.minHeight()) / 2; + _leftLabel.drawLeft(p, textPadding, leftTop, width, width); + const auto rightTop = (s.height() - _rightLabel.minHeight()) / 2; + _rightLabel.drawRight(p, textPadding, rightTop, rwidth, width); } _rightPixmap = std::move(rightPixmap); }