diff --git a/Telegram/Resources/icons/limits/filled_rating_crown.svg b/Telegram/Resources/icons/limits/filled_rating_crown.svg new file mode 100644 index 0000000000..e7cc2e124f --- /dev/null +++ b/Telegram/Resources/icons/limits/filled_rating_crown.svg @@ -0,0 +1,7 @@ + + + Icon / Filled / Folder / filled_rating_crown + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/limits/filled_understood.svg b/Telegram/Resources/icons/limits/filled_understood.svg new file mode 100644 index 0000000000..337b43cc71 --- /dev/null +++ b/Telegram/Resources/icons/limits/filled_understood.svg @@ -0,0 +1,7 @@ + + + Icon / Filled / Folder / filled_understood + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/rating_gifts.svg b/Telegram/Resources/icons/menu/rating_gifts.svg new file mode 100644 index 0000000000..ed3aa9d4b1 --- /dev/null +++ b/Telegram/Resources/icons/menu/rating_gifts.svg @@ -0,0 +1,11 @@ + + + Icon / Menu / rating_gifts + + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/rating_refund.svg b/Telegram/Resources/icons/menu/rating_refund.svg new file mode 100644 index 0000000000..05434da66a --- /dev/null +++ b/Telegram/Resources/icons/menu/rating_refund.svg @@ -0,0 +1,9 @@ + + + Icon / Menu / rating_refund + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/users_stars.svg b/Telegram/Resources/icons/menu/users_stars.svg new file mode 100644 index 0000000000..0a068f774a --- /dev/null +++ b/Telegram/Resources/icons/menu/users_stars.svg @@ -0,0 +1,10 @@ + + + Icon / Menu / users_stars + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2711328e0a..6bc8e0a3dc 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1876,8 +1876,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_star_ref_revoked_title" = "Link removed"; "lng_star_ref_revoked_text" = "It will no longer work."; -"lng_stars_rating_tooltip" = "Profile level reflects the user's payment reliability."; -"lng_stars_rating_learn_more" = "Learn More"; +"lng_stars_rating_title" = "Rating"; +"lng_stars_rating_future" = "Future Rating"; +"lng_stars_rating_subtitle" = "The rating update updates in 21 days after purchases."; +"lng_stars_rating_pending#one" = "{count} point is pending. {link}"; +"lng_stars_rating_pending#other" = "{count} points are pending. {link}"; +"lng_stars_rating_pending_preview" = "Preview {arrow}"; +"lng_stars_rating_pending_back" = "Back {arrow}"; +"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"; +"lng_stars_about_gifts_telegram" = "{emoji} 100% of the Stars spent on gifts purchased from Telegram."; +"lng_stars_title_gifts_users" = "Gifts and Posts from Users"; +"lng_stars_about_gifts_users" = "{emoji} 20% of the Stars spent on resold gifts, paid messages and channel posts."; +"lng_stars_title_refunds" = "Refunds and Conversions"; +"lng_stars_about_refunds" = "{emoji} 10x of refunded Stars and 85% of bought gifts converted to Stars."; +"lng_stars_rating_added" = "Added"; +"lng_stars_rating_deducted" = "Deducted"; +"lng_stars_rating_understood" = "Understood"; "lng_manage_discussion_group" = "Discussion"; "lng_manage_discussion_group_add" = "Add a group"; diff --git a/Telegram/SourceFiles/data/data_peer_common.h b/Telegram/SourceFiles/data/data_peer_common.h index 87d1722ab1..d3231c3be1 100644 --- a/Telegram/SourceFiles/data/data_peer_common.h +++ b/Telegram/SourceFiles/data/data_peer_common.h @@ -11,12 +11,12 @@ namespace Data { struct StarsRating { int level = 0; - int levelStars = 0; - int currentStars = 0; + int stars = 0; + int thisLevelStars = 0; int nextLevelStars = 0; explicit operator bool() const { - return level != 0 || levelStars != 0; + return level != 0 || thisLevelStars != 0; } friend inline bool operator==(StarsRating, StarsRating) = default; diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index dd4aa9b6bf..d3c09a06f6 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -72,8 +72,8 @@ bool ApplyBotVerifierSettings( const auto &data = rating->data(); return { .level = data.vlevel().v, - .levelStars = int(data.vcurrent_level_stars().v), - .currentStars = int(data.vstars().v), + .stars = int(data.vstars().v), + .thisLevelStars = int(data.vcurrent_level_stars().v), .nextLevelStars = int(data.vnext_level_stars().value_or_empty()), }; } diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 357efe9957..6904786101 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -1195,12 +1195,8 @@ StarsRating { margin: margins; padding: margins; style: TextStyle; - minSkip: pixels; - border: pixels; activeBg: color; - inactiveBg: color; activeFg: color; - inactiveFg: color; } infoStarsRating: StarsRating { margin: margins(6px, 6px, 6px, 6px); @@ -1208,24 +1204,37 @@ infoStarsRating: StarsRating { style: TextStyle(defaultTextStyle) { font: font(11px semibold); } - minSkip: 32px; - border: 2px; activeBg: windowBgActive; - inactiveBg: windowBgRipple; activeFg: windowFgActive; - inactiveFg: windowBoldFg; } -infoStarsRatingLearn: RoundButton(defaultActiveButton) { - width: -24px; - height: 44px; - textTop: 13px; - textFg: mediaviewTextLinkFg; - textFgOver: mediaviewTextLinkFg; - textBg: transparent; - textBgOver: transparent; - ripple: emptyRippleAnimation; +infoStarsTitle: FlatLabel(defaultFlatLabel) { + minWidth: 40px; + textFg: windowBoldFg; + maxHeight: 24px; + style: TextStyle(boxTextStyle) { + font: font(17px semibold); + } + align: align(top); +} +infoStarsFeatureTitle: FlatLabel(defaultFlatLabel) { + textFg: windowBoldFg; + style: semiboldTextStyle; + minWidth: 10px; + maxHeight: 20px; +} +infoStarsFeatureAbout: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; + minWidth: 20px; +} +infoStarsFeatureIconPosition: point(3px, 7px); +infoStarsFeatureMargin: margins(0px, 8px, 0px, 6px); +infoStarsFeatureLabelLeft: 46px; +infoStarsFeatureSkip: 2px; +infoStarsCrown: icon {{ "limits/filled_rating_crown-24x24", windowFgActive }}; +infoStarsUnderstood: IconEmoji{ + icon: icon {{ "limits/filled_understood-20x20", windowFgActive }}; + padding: margins(0px, -1px, 0px, 0px); } -infoStarsRatingTooltip: defaultImportantTooltip; collectionAbout: FlatLabel(defaultFlatLabel) { minWidth: 256px; diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index 691fcbbea4..33d4ab8dad 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -651,8 +651,9 @@ Cover::Cover( ? std::make_unique( this, st::infoStarsRating, - Data::StarsRatingValue(_peer), - _parentForTooltip) + _controller->uiShow(), + _peer->isSelf() ? QString() : _peer->shortName(), + Data::StarsRatingValue(_peer)) : nullptr) , _status(this, _st.status) , _showLastSeen(this, tr::lng_status_lastseen_when(), _st.showLastSeen) @@ -668,14 +669,6 @@ Cover::Cover( if (!_peer->isMegagroup()) { _status->setAttribute(Qt::WA_TransparentForMouseEvents); if (const auto rating = _starsRating.get()) { - _status->widthValue() | rpl::start_with_next([=](int width) { - rating->setMinimalAddedWidth(width); - }, rating->lifetime()); - const auto session = &_peer->session(); - rating->learnMoreRequests() | rpl::start_with_next([=] { - const auto &appConfig = session->appConfig(); - UrlClickHandler::Open(appConfig.starsRatingLearnMoreUrl()); - }, rating->lifetime()); _statusShift = rating->collapsedWidthValue(); _statusShift.changes() | rpl::start_with_next([=] { refreshStatusGeometry(width()); diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index 5f9183797e..844c68dfbd 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -246,12 +246,6 @@ QString AppConfig::ageVerifyBotUsername() const { return get(u"verify_age_bot_username"_q, QString()); } -QString AppConfig::starsRatingLearnMoreUrl() const { - return get( - u"stars_rating_learnmore_url"_q, - u"https://telegram.org/blog"_q); -} - int AppConfig::storiesAlbumsLimit() const { return get(u"stories_albums_limit"_q, 100); } diff --git a/Telegram/SourceFiles/main/main_app_config.h b/Telegram/SourceFiles/main/main_app_config.h index 11f6b66f34..91fac6690b 100644 --- a/Telegram/SourceFiles/main/main_app_config.h +++ b/Telegram/SourceFiles/main/main_app_config.h @@ -112,8 +112,6 @@ public: [[nodiscard]] int ageVerifyMinAge() const; [[nodiscard]] QString ageVerifyBotUsername() const; - [[nodiscard]] QString starsRatingLearnMoreUrl() const; - [[nodiscard]] int storiesAlbumsLimit() const; [[nodiscard]] int storiesAlbumLimit() const; diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.cpp b/Telegram/SourceFiles/ui/boxes/boost_box.cpp index d2cfeafca4..e72896a771 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/boost_box.cpp @@ -451,7 +451,7 @@ void BoostBox( : tr::lng_boost_channel_button(); }) | rpl::flatten_latest(); - const auto button = box->addButton(rpl::duplicate(submit), [=] { + box->addButton(rpl::duplicate(submit), [=] { if (state->submitted) { return; } else if (state->data.current().nextLevelBoosts > 0 @@ -519,17 +519,6 @@ void BoostBox( box->closeBox(); } }); - - rpl::combine( - std::move(submit), - box->widthValue() - ) | rpl::start_with_next([=](const QString &, int width) { - const auto &padding = st::boostBox.buttonPadding; - button->resizeToWidth(width - - padding.left() - - padding.right()); - button->moveToLeft(padding.left(), button->y()); - }, button->lifetime()); } object_ptr MakeLinkLabel( @@ -788,20 +777,10 @@ void AskBoostBox( data.group); auto submit = tr::lng_boost_channel_ask_button(); - const auto button = box->addButton(rpl::duplicate(submit), [=] { + box->addButton(rpl::duplicate(submit), [=] { QGuiApplication::clipboard()->setText(data.link); box->uiShow()->showToast(tr::lng_username_copied(tr::now)); }); - rpl::combine( - std::move(submit), - box->widthValue() - ) | rpl::start_with_next([=](const QString &, int width) { - const auto &padding = st::boostBox.buttonPadding; - button->resizeToWidth(width - - padding.left() - - padding.right()); - button->moveToLeft(padding.left(), button->y()); - }, button->lifetime()); } void FillBoostLimit( diff --git a/Telegram/SourceFiles/ui/controls/stars_rating.cpp b/Telegram/SourceFiles/ui/controls/stars_rating.cpp index 23ec63146f..5fe6397149 100644 --- a/Telegram/SourceFiles/ui/controls/stars_rating.cpp +++ b/Telegram/SourceFiles/ui/controls/stars_rating.cpp @@ -7,7 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/controls/stars_rating.h" +#include "info/profile/info_profile_icon.h" #include "lang/lang_keys.h" +#include "ui/effects/premium_bubble.h" +#include "ui/effects/premium_graphics.h" +#include "ui/layers/generic_box.h" +#include "ui/layers/show.h" +#include "ui/text/custom_emoji_helper.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/widgets/buttons.h" @@ -17,25 +23,395 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rp_widget.h" #include "ui/ui_utility.h" #include "styles/style_info.h" +#include "styles/style_layers.h" +#include "styles/style_premium.h" +#include "styles/style_settings.h" #include "styles/style_media_view.h" +#include "styles/style_menu_icons.h" namespace Ui { namespace { constexpr auto kAutoCollapseTimeout = 4 * crl::time(1000); +using Counters = Data::StarsRating; + +struct Feature { + const style::icon &icon; + QString title; + TextWithEntities about; +}; + +[[nodiscard]] object_ptr MakeFeature( + QWidget *parent, + Feature feature, + const Text::MarkedContext &context) { + auto result = object_ptr>( + parent, + object_ptr(parent), + st::infoStarsFeatureMargin); + const auto widget = result->entity(); + const auto icon = Ui::CreateChild( + widget, + feature.icon, + st::infoStarsFeatureIconPosition); + const auto title = Ui::CreateChild( + widget, + feature.title, + st::infoStarsFeatureTitle); + const auto about = Ui::CreateChild( + widget, + rpl::single(feature.about), + st::infoStarsFeatureAbout, + st::defaultPopupMenu, + context); + icon->show(); + title->show(); + about->show(); + widget->widthValue( + ) | rpl::start_with_next([=](int width) { + const auto left = st::infoStarsFeatureLabelLeft; + const auto available = width - left; + title->resizeToWidth(available); + about->resizeToWidth(available); + auto top = 0; + title->move(left, top); + top += title->height() + st::infoStarsFeatureSkip; + about->move(left, top); + top += about->height(); + widget->resize(width, top); + }, widget->lifetime()); + return result; +} + +[[nodiscard]] Fn CustomEmojiBadgeFactory( + const QString &text, + const style::color &bg, + const style::color &fg) { + return [=] { + auto string = Ui::Text::String( + st::settingsPremiumNewBadge.style, + text.toUpper()); + const auto size = QSize(string.maxWidth(), string.minHeight()); + const auto padding = st::settingsPremiumNewBadgePadding; + const auto full = size.grownBy(padding); + const auto ratio = style::DevicePixelRatio(); + + auto result = QImage( + full * ratio, + QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(ratio); + result.fill(Qt::transparent); + + auto p = QPainter(&result); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(bg); + + const auto r = padding.left(); + p.drawRoundedRect(0, 0, full.width(), full.height(), r, r); + + p.setPen(fg); + string.draw(p, { .position = { padding.left(), padding.top() } }); + + p.end(); + return result; + }; +} + +[[nodiscard]] Counters AdjustByReached(Counters data) { + const auto reached = !data.nextLevelStars; + if (reached) { + --data.level; + data.stars = data.nextLevelStars = std::max({ + data.stars, + data.thisLevelStars, + 1 + }); + data.thisLevelStars = 0; + } else { + data.stars = std::max(data.thisLevelStars, data.stars); + data.nextLevelStars = std::max( + data.nextLevelStars, + data.stars + 1); + } + return data; +} + +[[nodiscard]] Fn BubbleTextFactory(int countForScale) { + return [=](int count) { + return (countForScale < 10'000) + ? QString::number(count) + : (countForScale < 10'000'000) + ? (QString::number((count / 100) / 10.) + 'K') + : (QString::number((count / 100'000) / 10.) + 'M'); + }; +} + +void FillRatingLimit( + rpl::producer<> showFinished, + not_null container, + rpl::producer data, + style::margins limitLinePadding, + int starsForScale) { + const auto addSkip = [&](int skip) { + container->add(object_ptr(container, skip)); + }; + + const auto ratio = [=](Counters rating) { + const auto min = rating.thisLevelStars; + const auto max = rating.nextLevelStars; + + Assert(rating.stars >= min && rating.stars <= max); + const auto count = (max - min); + const auto index = (rating.stars - min); + if (!index) { + return 0.; + } else if (index == count) { + return 1.; + } else if (count == 2) { + return 0.5; + } + const auto available = st::boxWideWidth + - st::boxPadding.left() + - st::boxPadding.right(); + const auto average = available / float64(count); + const auto levelWidth = [&](int add) { + return st::normalFont->width( + tr::lng_boost_level( + tr::now, + lt_count, + rating.level + add)); + }; + const auto paddings = 2 * st::premiumLineTextSkip; + const auto labelLeftWidth = paddings + levelWidth(0); + const auto labelRightWidth = paddings + levelWidth(1); + const auto first = std::max(average, labelLeftWidth * 1.); + const auto last = std::max(average, labelRightWidth * 1.); + const auto other = (available - first - last) / (count - 2); + return (first + (index - 1) * other) / available; + }; + + auto adjustedData = rpl::duplicate(data) | rpl::map(AdjustByReached); + + auto bubbleRowState = rpl::duplicate( + adjustedData + ) | rpl::combine_previous( + Counters() + ) | rpl::map([=](Counters previous, Counters counters) { + return Premium::BubbleRowState{ + .counter = counters.stars, + .ratio = ratio(counters), + .animateFromZero = (counters.level != previous.level), + .dynamic = true, + }; + }); + Premium::AddBubbleRow( + container, + st::boostBubble, + std::move(showFinished), + rpl::duplicate(bubbleRowState), + Premium::BubbleType::StarRating, + BubbleTextFactory(starsForScale), + &st::infoStarsCrown, + limitLinePadding); + addSkip(st::premiumLineTextSkip); + + const auto level = [](int level) { + return tr::lng_boost_level(tr::now, lt_count, level); + }; + auto limitState = std::move( + bubbleRowState + ) | rpl::map([](const Premium::BubbleRowState &state) { + return Premium::LimitRowState{ + .ratio = state.ratio, + .animateFromZero = state.animateFromZero, + .dynamic = state.dynamic + }; + }); + auto left = rpl::duplicate( + adjustedData + ) | rpl::map([=](Counters counters) { + return level(counters.level); + }); + auto right = rpl::duplicate( + adjustedData + ) | rpl::map([=](Counters counters) { + return level(counters.level + 1); + }); + Premium::AddLimitRow( + container, + st::boostLimits, + Premium::LimitRowLabels{ + .leftLabel = std::move(left), + .rightLabel = std::move(right), + .activeLineBg = [=] { return st::windowBgActive->b; }, + }, + std::move(limitState), + limitLinePadding); +} + +object_ptr MakeBoostFeaturesBadge( + not_null parent, + rpl::producer text, + Fn bg) { + auto result = object_ptr( + parent, + std::move(text), + st::boostLevelBadge); + const auto label = result.data(); + + label->show(); + label->paintRequest() | rpl::start_with_next([=] { + const auto size = label->textMaxWidth(); + const auto rect = QRect( + (label->width() - size) / 2, + st::boostLevelBadge.margin.top(), + size, + st::boostLevelBadge.style.font->height + ).marginsAdded(st::boostLevelBadge.margin); + auto p = QPainter(label); + auto hq = PainterHighQualityEnabler(p); + p.setBrush(bg(rect)); + p.setPen(Qt::NoPen); + p.drawRoundedRect(rect, rect.height() / 2., rect.height() / 2.); + + const auto &lineFg = st::windowBgRipple; + const auto line = st::boostLevelBadgeLine; + const auto top = st::boostLevelBadge.margin.top() + + ((st::boostLevelBadge.style.font->height - line) / 2); + const auto left = 0; + const auto skip = st::boostLevelBadgeSkip; + if (const auto right = rect.x() - skip; right > left) { + p.fillRect(left, top, right - left, line, lineFg); + } + const auto right = label->width(); + if (const auto left = rect.x() + rect.width() + skip + ; left < right) { + p.fillRect(left, top, right - left, line, lineFg); + } + }, label->lifetime()); + + return result; +} + +void AboutRatingBox( + not_null box, + const QString &name, + Counters data) { + box->setWidth(st::boxWideWidth); + box->setStyle(st::boostBox); + + struct State { + rpl::variable data; + rpl::variable full; + }; + const auto state = box->lifetime().make_state(); + state->data = std::move(data); + + FillRatingLimit( + BoxShowFinishes(box), + box->verticalLayout(), + state->data.value(), + st::boxRowPadding, + data.stars); + + box->setMaxHeight(st::boostBoxMaxHeight); + const auto close = box->addTopButton( + st::boxTitleClose, + [=] { box->closeBox(); }); + + auto title = tr::lng_stars_rating_title();; + + auto text = !name.isEmpty() + ? tr::lng_stars_rating_about( + lt_name, + rpl::single(TextWithEntities{ name }), + Ui::Text::RichLangValue) | rpl::type_erased() + : tr::lng_stars_rating_about_your( + Ui::Text::RichLangValue) | rpl::type_erased(); + + box->addRow( + object_ptr(box, std::move(title), st::infoStarsTitle), + st::boxRowPadding + QMargins(0, st::boostTitleSkip / 2, 0, 0)); + + const auto aboutLabel = box->addRow( + object_ptr( + box, + std::move(text), + st::boostText), + (st::boxRowPadding + + QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip))); + aboutLabel->setTryMakeSimilarLines(true); + + auto helper = Ui::Text::CustomEmojiHelper(); + const auto makeBadge = [&]( + const QString &text, + const style::color &bg, + const style::color &fg) { + return helper.paletteDependent( + CustomEmojiBadgeFactory(text, bg, fg), + st::badgeEmojiMargin); + }; + const auto makeActive = [&](const QString &text) { + return makeBadge(text, st::windowBgActive, st::windowFgActive); + }; + const auto makeInactive = [&](const QString &text) { + return makeBadge(text, st::windowSubTextFg, st::windowFgActive); + }; + const auto features = std::vector{ + { + st::menuIconRatingGifts, + tr::lng_stars_title_gifts_telegram(tr::now), + tr::lng_stars_about_gifts_telegram( + tr::now, + lt_emoji, + makeActive(tr::lng_stars_rating_added(tr::now)), + Ui::Text::RichLangValue), + }, + { + st::menuIconRatingUsers, + tr::lng_stars_title_gifts_users(tr::now), + tr::lng_stars_about_gifts_users( + tr::now, + lt_emoji, + makeActive(tr::lng_stars_rating_added(tr::now)), + Ui::Text::RichLangValue), + }, + { + st::menuIconRatingRefund, + tr::lng_stars_title_refunds(tr::now), + tr::lng_stars_about_refunds( + tr::now, + lt_emoji, + makeInactive(tr::lng_stars_rating_deducted(tr::now)), + Ui::Text::RichLangValue), + }, + }; + const auto context = helper.context(); + for (const auto &feature : features) { + box->addRow(MakeFeature(box, feature, context)); + } + box->addButton(rpl::single(QString()), [=] { + box->closeBox(); + })->setText(rpl::single(Ui::Text::IconEmoji( + &st::infoStarsUnderstood + ).append(' ').append(tr::lng_stars_rating_understood(tr::now)))); +} + } // namespace StarsRating::StarsRating( QWidget *parent, const style::StarsRating &st, - rpl::producer value, - Fn()> parentForTooltip) + std::shared_ptr show, + const QString &name, + rpl::producer value) : _widget(std::make_unique(parent)) , _st(st) -, _parentForTooltip(std::move(parentForTooltip)) -, _value(std::move(value)) -, _collapseTimer([=] { _expanded = false; }) { +, _show(std::move(show)) +, _name(name) +, _value(std::move(value)) { init(); } @@ -43,18 +419,6 @@ StarsRating::~StarsRating() = default; void StarsRating::init() { _widget->setPointerCursor(true); - _expanded.changes() | rpl::start_with_next([=](bool expanded) { - _widget->setPointerCursor(!expanded); - const auto from = expanded ? 0. : 1.; - const auto till = expanded ? 1. : 0.; - _expandedAnimation.start([=] { - updateWidth(); - if (!_expandedAnimation.animating()) { - updateStarsTooltipGeometry(); - } - }, from, till, st::slideDuration); - toggleTooltips(expanded); - }, lifetime()); _widget->paintRequest() | rpl::start_with_next([=] { auto p = QPainter(_widget.get()); @@ -65,189 +429,43 @@ void StarsRating::init() { if (!_value.current()) { return; } - _expanded = true; - _collapseTimer.callOnce(kAutoCollapseTimeout); + _show->show(Box(AboutRatingBox, _name, _value.current())); }); const auto added = _st.margin + _st.padding; - const auto border = 2 * _st.border; const auto fontHeight = _st.style.font->height; - const auto height = added.top() + fontHeight + added.bottom() + border; + const auto height = added.top() + fontHeight + added.bottom(); _widget->resize(_widget->width(), height); - _value.value() | rpl::start_with_next([=](Data::StarsRating rating) { + _value.value() | rpl::start_with_next([=](Counters rating) { if (!rating) { _widget->resize(0, _widget->height()); _collapsedWidthValue = 0; - _expanded = false; - updateExpandedWidth(); - _expandedAnimation.stop(); return; } updateTexts(rating); }, lifetime()); } -void StarsRating::updateTexts(Data::StarsRating rating) { +void StarsRating::updateTexts(Counters rating) { _collapsedText.setText( _st.style, Lang::FormatCountDecimal(rating.level)); - _expandedText.setText( - _st.style, - tr::lng_boost_level(tr::now, lt_count_decimal, rating.level)); - _nextText.setText( - _st.style, - (rating.nextLevelStars - ? Lang::FormatCountDecimal(rating.level + 1) - : QString())); const auto added = _st.padding; - const auto border = 2 * _st.border; - const auto add = added.left() + added.right() + border; - const auto min = _expandedText.maxWidth() + _nextText.maxWidth(); + const auto add = added.left() + added.right(); const auto height = _widget->height(); - _minimalContentWidth = add + min + _st.minSkip; _collapsedWidthValue = _st.margin.right() + std::max( add + _collapsedText.maxWidth(), height - _st.margin.top() - _st.margin.bottom()); - updateExpandedWidth(); updateWidth(); } -void StarsRating::updateExpandedWidth() { - _expandedWidthValue = _st.margin.right() + std::max( - _collapsedWidthValue.current() + _minimalAddedWidth.current(), - _minimalContentWidth.current()); -} - void StarsRating::updateWidth() { - const auto widthToRight = anim::interpolate( - _collapsedWidthValue.current(), - _expandedWidthValue.current(), - _expandedAnimation.value(_expanded.current() ? 1. : 0.)); + const auto widthToRight = _collapsedWidthValue.current(); _widget->resize(_st.margin.left() + widthToRight, _widget->height()); _widget->update(); - updateStarsTooltipGeometry(); -} - -void StarsRating::toggleTooltips(bool shown) { - if (!shown) { - if (const auto strong = _about.get()) { - strong->hideAnimated(); - } - if (const auto strong = _stars.release()) { - strong->toggleAnimated(false); - } - return; - } - const auto value = _value.current(); - const auto parent = _parentForTooltip - ? _parentForTooltip().get() - : _widget->window(); - const auto text = value.nextLevelStars - ? (Lang::FormatCountDecimal(value.currentStars) - + u" / "_q - + Lang::FormatCountDecimal(value.nextLevelStars)) - : Lang::FormatCountDecimal(value.currentStars); - _stars = std::make_unique( - parent, - Ui::MakeNiceTooltipLabel( - _widget.get(), - rpl::single(TextWithEntities{ text }), - st::storiesInfoTooltipMaxWidth, - st::storiesInfoTooltipLabel), - st::infoStarsRatingTooltip); - const auto stars = _stars.get(); - const auto weak = QPointer(stars); - const auto destroy = [=] { - delete weak.data(); - }; - stars->setAttribute(Qt::WA_TransparentForMouseEvents); - stars->setHiddenCallback(destroy); - updateStarsTooltipGeometry(); - stars->toggleAnimated(true); - - _aboutSt = std::make_unique(st::defaultMultilineToast); - const auto learn = tr::lng_stars_rating_learn_more(tr::now); - _aboutSt->padding.setRight( - (st::infoStarsRatingLearn.style.font->width(learn) - - st::infoStarsRatingLearn.width)); - - _about = Ui::Toast::Show(parent, { - .text = tr::lng_stars_rating_tooltip( - tr::now, - Ui::Text::WithEntities), - .st = _aboutSt.get(), - .attach = RectPart::Top, - .dark = true, - .adaptive = true, - .acceptinput = true, - .duration = kAutoCollapseTimeout, - }); - const auto strong = _about.get(); - if (!strong) { - return; - } - const auto widget = strong->widget(); - const auto hideToast = [weak = _about] { - if (const auto strong = weak.get()) { - strong->hideAnimated(); - } - }; - - const auto button = Ui::CreateChild( - widget.get(), - rpl::single(learn), - st::infoStarsRatingLearn); - button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); - button->show(); - rpl::combine( - widget->sizeValue(), - button->sizeValue() - ) | rpl::start_with_next([=](QSize outer, QSize inner) { - button->moveToRight( - 0, - (outer.height() - inner.height()) / 2, - outer.width()); - }, widget->lifetime()); - button->setClickedCallback([=] { - _learnMoreRequests.fire({}); - }); -} - -void StarsRating::updateStarsTooltipGeometry() { - if (!_stars) { - return; - } - const auto weakParent = base::make_weak(_stars->parentWidget()); - const auto weak = base::make_weak(_widget.get()); - const auto point = _st.margin.left() - + _st.border - + (_activeWidth / (_value.current().nextLevelStars ? 1 : 2)); - const auto countPosition = [=](QSize size) { - const auto strong = weak.get(); - const auto parent = weakParent.get(); - if (!strong || !parent) { - return QPoint(); - } - const auto geometry = Ui::MapFrom(parent, strong, strong->rect()); - const auto shift = size.width() / 2; - const auto left = geometry.x() + point - shift; - const auto margin = st::defaultImportantTooltip.margin; - return QPoint( - std::min( - std::max(left, margin.left()), - parent->width() - size.width() - margin.right()), - geometry.y() + geometry.height()); - }; - _stars->pointAt( - Ui::MapFrom( - _stars->parentWidget(), - _widget.get(), - QRect(point, 0, st::lineWidth, _widget->height())), - RectPart::Bottom, - countPosition); } void StarsRating::raise() { @@ -263,92 +481,27 @@ void StarsRating::paint(QPainter &p) { if (outer.isEmpty()) { return; } - const auto border = _st.border; - const auto middle = outer.marginsRemoved( - { border, border, border, border }); - const auto mradius = middle.height() / 2.; - const auto inner = middle.marginsRemoved(_st.padding); - - const auto expanded = _expandedAnimation.value( - _expanded.current() ? 1. : 0.); + const auto inner = outer.marginsRemoved(_st.padding); auto hq = PainterHighQualityEnabler(p); p.setPen(Qt::NoPen); - p.setBrush(_st.inactiveBg); - const auto oradius = outer.height() / 2.; - p.drawRoundedRect(outer, oradius, oradius); p.setBrush(_st.activeBg); const auto value = _value.current(); - const auto expandedRatio = (value.nextLevelStars > value.levelStars) - ? ((value.currentStars - value.levelStars) - / float64(value.nextLevelStars - value.levelStars)) - : 1.; - const auto expandedFilled = (expandedRatio < 1.) - ? (_st.padding.left() - + _expandedText.maxWidth() - + _st.padding.right() - + expandedRatio * (middle.width() - - _st.padding.left() - - _expandedText.maxWidth() - - _st.padding.right() - - _st.padding.left() - - _nextText.maxWidth() - - _st.padding.right())) - : middle.width(); - const auto collapsedFilled = _collapsedWidthValue.current() - - _st.margin.right() - - 2 * _st.border; - _activeWidth = anim::interpolate( - collapsedFilled, - expandedFilled, - expanded); - p.drawRoundedRect( - middle.x(), - middle.y(), - _activeWidth, - middle.height(), - mradius, - mradius); + const auto radius = outer.height() / 2.; + p.drawRoundedRect(outer, radius, radius); p.setPen(_st.activeFg); - if (expanded < 1.) { - p.setOpacity(1. - expanded); - const auto skip = (inner.width() - _collapsedText.maxWidth()) / 2; - _collapsedText.draw(p, { - .position = inner.topLeft() + QPoint(skip, 0), - .availableWidth = _collapsedText.maxWidth(), - }); - } - if (expanded > 0.) { - p.setOpacity(expanded); - _expandedText.draw(p, { - .position = inner.topLeft(), - .availableWidth = _expandedText.maxWidth(), - }); - - p.setPen(_st.inactiveFg); - _nextText.draw(p, { - .position = (inner.topLeft() - + QPoint(inner.width() - _nextText.maxWidth(), 0)), - .availableWidth = _nextText.maxWidth(), - }); - } -} - -void StarsRating::setMinimalAddedWidth(int addedWidth) { - _minimalAddedWidth = addedWidth + (_st.style.font->spacew * 2); - updateExpandedWidth(); - updateWidth(); + const auto skip = (inner.width() - _collapsedText.maxWidth()) / 2; + _collapsedText.draw(p, { + .position = inner.topLeft() + QPoint(skip, 0), + .availableWidth = _collapsedText.maxWidth(), + }); } rpl::producer StarsRating::collapsedWidthValue() const { return _collapsedWidthValue.value(); } -rpl::producer<> StarsRating::learnMoreRequests() const { - return _learnMoreRequests.events(); -} - rpl::lifetime &StarsRating::lifetime() { return _widget->lifetime(); } diff --git a/Telegram/SourceFiles/ui/controls/stars_rating.h b/Telegram/SourceFiles/ui/controls/stars_rating.h index 7af2a41c62..b72f061d8f 100644 --- a/Telegram/SourceFiles/ui/controls/stars_rating.h +++ b/Telegram/SourceFiles/ui/controls/stars_rating.h @@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "base/timer.h" #include "base/weak_ptr.h" #include "data/data_peer_common.h" @@ -25,22 +24,22 @@ namespace Ui { class ImportantTooltip; class AbstractButton; class FlatLabel; +class Show; class StarsRating final { public: StarsRating( QWidget *parent, const style::StarsRating &st, - rpl::producer value, - Fn()> parentForTooltip); + std::shared_ptr show, + const QString &name, + rpl::producer value); ~StarsRating(); void raise(); void moveTo(int x, int y); - void setMinimalAddedWidth(int addedWidth); [[nodiscard]] rpl::producer collapsedWidthValue() const; - [[nodiscard]] rpl::producer<> learnMoreRequests() const; [[nodiscard]] rpl::lifetime &lifetime(); @@ -48,35 +47,17 @@ private: void init(); void paint(QPainter &p); void updateTexts(Data::StarsRating rating); - void updateExpandedWidth(); void updateWidth(); - void toggleTooltips(bool shown); - void updateStarsTooltipGeometry(); const std::unique_ptr _widget; const style::StarsRating &_st; - const Fn()> _parentForTooltip; - - std::unique_ptr _aboutSt; - base::weak_ptr _about; - std::unique_ptr _stars; + const std::shared_ptr _show; + const QString _name; Ui::Text::String _collapsedText; - Ui::Text::String _expandedText; - Ui::Text::String _nextText; rpl::variable _value; rpl::variable _collapsedWidthValue; - rpl::variable _expandedWidthValue; - rpl::variable _minimalContentWidth; - rpl::variable _minimalAddedWidth; - rpl::variable _expanded = false; - Ui::Animations::Simple _expandedAnimation; - mutable int _activeWidth = 0; - - rpl::event_stream<> _learnMoreRequests; - - base::Timer _collapseTimer; }; diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index 4abbea6f14..f5353f100c 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -294,6 +294,7 @@ boostBottomSkip: 6px; boostBox: Box(premiumPreviewDoubledLimitsBox) { buttonPadding: margins(16px, 12px, 16px, 12px); buttonHeight: 42px; + buttonWide: true; button: RoundButton(defaultActiveButton) { height: 42px; textTop: 12px; diff --git a/Telegram/SourceFiles/ui/effects/premium_bubble.cpp b/Telegram/SourceFiles/ui/effects/premium_bubble.cpp index 6106201af4..a507f78816 100644 --- a/Telegram/SourceFiles/ui/effects/premium_bubble.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_bubble.cpp @@ -422,7 +422,8 @@ void BubbleWidget::paintEvent(QPaintEvent *e) { _bubble.paintBubble(p, bubbleRect, [&] { switch (_type) { - case BubbleType::NoPremium: return st::windowBgActive->b; + case BubbleType::NoPremium: + case BubbleType::StarRating: return st::windowBgActive->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 88e37771d0..404a75abef 100644 --- a/Telegram/SourceFiles/ui/effects/premium_bubble.h +++ b/Telegram/SourceFiles/ui/effects/premium_bubble.h @@ -88,6 +88,7 @@ struct BubbleRowState { }; enum class BubbleType : uchar { + StarRating, NoPremium, Premium, Credits, diff --git a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp index 5b277429d4..2ace588b1f 100644 --- a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp @@ -504,9 +504,17 @@ void AddLimitRow( LimitRowLabels labels, rpl::producer state, const style::margins &padding) { - parent->add( + const auto color = std::move(labels.activeLineBg); + const auto line = parent->add( object_ptr(parent, st, std::move(labels), std::move(state)), padding); + if (color) { + line->setColorOverride(color()); + + style::PaletteChanged() | rpl::start_with_next([=] { + line->setColorOverride(color()); + }, line->lifetime()); + } } void AddAccountsRow( diff --git a/Telegram/SourceFiles/ui/effects/premium_graphics.h b/Telegram/SourceFiles/ui/effects/premium_graphics.h index 86b136457b..049788ddc0 100644 --- a/Telegram/SourceFiles/ui/effects/premium_graphics.h +++ b/Telegram/SourceFiles/ui/effects/premium_graphics.h @@ -65,6 +65,7 @@ struct LimitRowLabels { rpl::producer leftCount; rpl::producer rightLabel; rpl::producer rightCount; + Fn activeLineBg; }; struct LimitRowState { diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index 3c37626681..f3c1eb27b4 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -185,7 +185,10 @@ menuIconPayment: icon {{ "payments/payment_card", menuIconColor }}; menuIconOrderPrice: icon {{ "menu/order_price", menuIconColor }}; menuIconOrderDate: icon {{ "menu/order_date", menuIconColor }}; menuIconOrderNumber: icon {{ "menu/order_number", menuIconColor }}; -menuIconAdd: icon{{ "menu/add", menuIconColor }}; +menuIconAdd: icon {{ "menu/add", menuIconColor }}; +menuIconRatingGifts: icon {{ "menu/rating_gifts-24x24", menuIconColor }}; +menuIconRatingUsers: icon {{ "menu/users_stars-24x24", menuIconColor }}; +menuIconRatingRefund: icon {{ "menu/rating_refund-24x24", menuIconColor }}; menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }}; menuIconTTLAnyTextPosition: point(11px, 22px); diff --git a/Telegram/lib_ui b/Telegram/lib_ui index ea3c209939..cd01ab20e4 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit ea3c209939fc10e93750c4207f5aa1fe6af66e99 +Subproject commit cd01ab20e485aabd0991bb43c7d8302cd0a2ebc4