diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 74ff652df4..925832be7f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4677,6 +4677,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_paid_comment_limit_about#other" = "characters"; "lng_paid_comment_emoji_about#one" = "emoji"; "lng_paid_comment_emoji_about#other" = "emoji"; +"lng_paid_reaction_title" = "React with Stars"; +"lng_paid_reaction_about" = "Highlight and pin your message by sending Stars to {name}."; +"lng_paid_reaction_button" = "Send {stars}"; "lng_sensitive_tag" = "18+"; "lng_sensitive_title" = "18+"; diff --git a/Telegram/SourceFiles/calls/group/calls_group_stars_box.cpp b/Telegram/SourceFiles/calls/group/calls_group_stars_box.cpp index 7704f5a435..6945057b02 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_stars_box.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_stars_box.cpp @@ -34,15 +34,18 @@ void VideoStreamStarsBox( VideoStreamStarsBoxArgs &&args) { args.show->session().credits().load(); + const auto sending = args.sending; auto submitText = [=](rpl::producer amount) { auto nice = std::move(amount) | rpl::map([=](int count) { return Ui::CreditsEmojiSmall().append( Lang::FormatCountDecimal(count)); }); - return tr::lng_paid_comment_button( - lt_stars, - std::move(nice), - Ui::Text::RichLangValue); + return (sending + ? tr::lng_paid_reaction_button + : tr::lng_paid_comment_button)( + lt_stars, + std::move(nice), + Ui::Text::RichLangValue); }; const auto &show = args.show; const auto session = &show->session(); @@ -78,15 +81,20 @@ void VideoStreamStarsBox( .my = (entry.my == 1), }); }; - top.reserve(2); + + top.reserve(args.top.size() + 1); + for (const auto &entry : args.top) { + add(entry); + } + auto myAdded = base::flat_set(); const auto i = ranges::find(top, true, &Ui::PaidReactionTop::my); if (i != end(top)) { myAdded.emplace(i->barePeerId); } const auto myCount = uint32((i != end(top)) ? i->count : 0); - const auto myAdd = [&](PeerData *peer) { - const auto barePeerId = peer ? uint64(peer->id.value) : 0; + const auto myAdd = [&](not_null peer) { + const auto barePeerId = uint64(peer->id.value); if (!myAdded.emplace(barePeerId).second) { return; } @@ -97,7 +105,6 @@ void VideoStreamStarsBox( }); }; myAdd(session->user()); - myAdd(nullptr); ranges::stable_sort(top, ranges::greater(), &Ui::PaidReactionTop::count); const auto weak = base::make_weak(box); Ui::PaidReactionsBox(box, { @@ -114,7 +121,8 @@ void VideoStreamStarsBox( strong->closeBox(); } }, - .videoStreamChoosing = true, + .videoStreamChoosing = !sending, + .videoStreamSending = sending, .dark = true, }); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_stars_box.h b/Telegram/SourceFiles/calls/group/calls_group_stars_box.h index 7814c6d871..6e50b68fe6 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_stars_box.h +++ b/Telegram/SourceFiles/calls/group/calls_group_stars_box.h @@ -11,6 +11,10 @@ namespace ChatHelpers { class Show; } // namespace ChatHelpers +namespace Data { +struct MessageReactionsTopPaid; +} // namespace Data + namespace Ui { class BoxContent; class GenericBox; @@ -20,7 +24,9 @@ namespace Calls::Group { struct VideoStreamStarsBoxArgs { std::shared_ptr show; + std::vector top; int current = 0; + bool sending = false; Fn save; QString name; }; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 6b53b676fa..a915826793 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_drafts.h" #include "data/data_messages.h" +#include "data/data_message_reactions.h" #include "data/data_saved_sublist.h" #include "data/data_session.h" #include "data/data_user.h" @@ -1306,13 +1307,38 @@ void ComposeControls::setStarsReactionCounter( updateControlsGeometry(_wrap->size()); }, _starsReaction->lifetime()); - _starsReaction->setClickedCallback([=] { - _starsReactionIncrements.fire({}); - }); + _starsReaction->setAcceptBoth(); + _starsReaction->clicks( + ) | rpl::start_with_next([=](Qt::MouseButton button) { + if (button == Qt::LeftButton) { + _starsReactionIncrements.fire({ .count = 1 }); + } else { + _show->show(Calls::Group::MakeVideoStreamStarsBox({ + .show = _show, + .top = _starsReactionTop.current(), + .current = 0, + .sending = true, + .save = crl::guard(_starsReaction, [=](int count) { + _starsReactionIncrements.fire({ + .count = count, + .fromBox = true, + }); + }), + .name = _history ? _history->peer->shortName() : QString(), + })); + + } + }, _starsReaction->lifetime()); } } -rpl::producer<> ComposeControls::starsReactionIncrements() const { +void ComposeControls::setStarsReactionTop( + rpl::producer> top) { + _starsReactionTop = std::move(top); +} + +auto ComposeControls::starsReactionIncrements() const +-> rpl::producer { return _starsReactionIncrements.events(); } @@ -2465,18 +2491,7 @@ void ComposeControls::initSendButton() { _send->clicks( ) | rpl::start_with_next([=] { - const auto type = _send->type(); - if (type == Ui::SendButton::Type::EditPrice) { - _show->show(Calls::Group::MakeVideoStreamStarsBox({ - .show = _show, - .current = _chosenStarsCount.value_or(0), - .save = crl::guard(_send, [=](int count) { - _chosenStarsCount = count; - updateSendButtonType(); - }), - .name = _history ? _history->peer->name() : QString(), - })); - } else if (type == Ui::SendButton::Type::Cancel) { + if (_send->type() == Ui::SendButton::Type::Cancel) { cancelInlineBot(); } }, _send->lifetime()); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index ad164dc49a..d0f9787682 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -47,6 +47,7 @@ struct Draft; class DraftKey; class PhotoMedia; struct WebPageDraft; +struct MessageReactionsTopPaid; } // namespace Data namespace InlineBots { @@ -167,7 +168,15 @@ public: [[nodiscard]] rpl::producer<> commentsShownToggles() const; void setStarsReactionCounter( rpl::producer count); - [[nodiscard]] rpl::producer<> starsReactionIncrements() const; + using StarReactionTop = Data::MessageReactionsTopPaid; + void setStarsReactionTop( + rpl::producer> top); + struct StarReactionIncrement { + int count = 0; + bool fromBox = false; + }; + [[nodiscard]] auto starsReactionIncrements() const + -> rpl::producer; bool focus(); [[nodiscard]] bool focused() const; @@ -450,7 +459,8 @@ private: rpl::event_stream<> _focusRequests; rpl::event_stream<> _showScheduledRequests; rpl::event_stream<> _commentsShownToggles; - rpl::event_stream<> _starsReactionIncrements; + rpl::event_stream _starsReactionIncrements; + rpl::variable> _starsReactionTop; rpl::variable _recording; rpl::variable _hasSendText; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 525a7777f4..60257ae395 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -1771,13 +1771,13 @@ auto Controller::starsReactionsValue() const }); } -void Controller::setStarsReactionIncrements(rpl::producer<> increments) { +void Controller::setStarsReactionIncrements(rpl::producer increments) { std::move( increments - ) | rpl::start_with_next([=] { + ) | rpl::start_with_next([=](int count) { if (const auto call = _videoStreamCall.get()) { const auto show = _delegate->storiesShow(); - Payments::TryAddingPaidReaction(call, 1, std::nullopt, show); + Payments::TryAddingPaidReaction(call, count, std::nullopt, show); } }, _videoStreamLifetime); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index a0954c1324..492888e2ca 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -186,7 +186,7 @@ public: void setCommentsShownToggles(rpl::producer<> toggles); [[nodiscard]] auto starsReactionsValue() const -> rpl::producer; - void setStarsReactionIncrements(rpl::producer<> increments); + void setStarsReactionIncrements(rpl::producer increments); void unfocusReply(); void shareRequested(); diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index e773f52f26..eca1c371bc 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_group_call.h" #include "data/data_message_reaction_id.h" +#include "data/data_message_reactions.h" #include "data/data_peer_values.h" #include "data/data_session.h" #include "data/data_user.h" @@ -863,12 +864,16 @@ void ReplyArea::show( : nullptr); _controller->setCommentsShownToggles( _controls->commentsShownToggles()); - _controls->setStarsReactionCounter(_data.videoStream - ? _controller->starsReactionsValue() - : nullptr); } + using Controls = HistoryView::ComposeControls; + _controls->setStarsReactionCounter(_data.videoStream + ? _controller->starsReactionsValue() + : nullptr); _controller->setStarsReactionIncrements( - _controls->starsReactionIncrements()); + _controls->starsReactionIncrements( + ) | rpl::map([](Controls::StarReactionIncrement increment) { + return increment.count; + })); if (!peerChanged) { if (_data.peer) { _controls->clear(); @@ -951,6 +956,22 @@ void ReplyArea::show( void ReplyArea::updateVideoStream(not_null videoStream) { _type = ReplyAreaType::VideoStreamComment; _videoStream = videoStream; + const auto messages = videoStream->messages(); + _controls->setStarsReactionTop(rpl::single(rpl::empty) | rpl::then( + messages->starsValueChanges() + ) | rpl::map([=] { + const auto &list = messages->starsTop().topDonors; + auto result = std::vector(); + result.reserve(list.size()); + for (const auto &item : list) { + result.push_back({ + .peer = item.peer, + .count = uint32(item.stars), + .my = item.my ? 1U : 0U, + }); + } + return result; + })); } bool ReplyArea::showSlowmodeError() { diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index 2cc5fe5ea2..6ae84c044a 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -663,7 +663,7 @@ storiesEmojiPan: EmojiPan(defaultEmojiPan) { menuBelow: icon {{ "menu/link_below", storiesComposeWhiteText }}; menuAbove: icon {{ "menu/link_above", storiesComposeWhiteText }}; menuPrice: icon {{ "menu/earn", storiesComposeWhiteText }}; - menuEditStars: icon {{ "chat/input_paid", storiesComposeWhiteText }}; + menuEditStars: icon {{ "chat/input_paid", storiesComposeWhiteText, point(-8px, -8px) }}; stripBubble: icon{ { "chat/reactions_bubble_shadow", windowShadowFg }, diff --git a/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp b/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp index 31d296a62b..83d615d75b 100644 --- a/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp @@ -219,12 +219,15 @@ void PaidReactionSlider( }, stars->lifetime()); } -[[nodiscard]] QImage GenerateBadgeImage(int count) { +[[nodiscard]] QImage GenerateBadgeImage(int count, bool videoStream) { return GenerateSmallBadgeImage( Lang::FormatCountDecimal(count), st::paidReactTopStarIcon, - st::creditsBg3->c, - st::premiumButtonFg->c); + (videoStream + ? Ui::ColorFromSerialized( + Calls::Group::Ui::StarsColoringForCount(count).bgLight) + : st::creditsBg3->c), + videoStream ? st::white->c : st::premiumButtonFg->c); } void AddArrowDown(not_null widget) { @@ -254,7 +257,8 @@ void AddArrowDown(not_null widget) { [[nodiscard]] not_null MakeTopReactor( not_null parent, const PaidReactionTop &data, - Fn selectShownPeer) { + Fn selectShownPeer, + bool videoStream) { const auto result = CreateChild(parent); result->show(); if (data.click && !data.my) { @@ -288,7 +292,7 @@ void AddArrowDown(not_null widget) { p.drawImage(left, 0, photo->image(st::paidReactTopUserpic)); if (state->badge.isNull()) { - state->badge = GenerateBadgeImage(count); + state->badge = GenerateBadgeImage(count, videoStream); } const auto bwidth = state->badge.width() / state->badge.devicePixelRatio(); @@ -297,7 +301,7 @@ void AddArrowDown(not_null widget) { st::paidReactTopBadgeSkip, state->badge); - p.setPen(st::windowFg); + p.setPen(videoStream ? st::groupCallMembersFg : st::windowFg); const auto skip = st::normalFont->spacew; const auto nameTop = st::paidReactTopNameSkip; const auto available = result->width() - skip * 2; @@ -359,17 +363,19 @@ void FillTopReactors( std::vector top, rpl::producer chosen, rpl::producer shownPeer, - Fn changeShownPeer) { - const auto badge = container->add( - object_ptr>( - container, - MakeBoostFeaturesBadge( + Fn changeShownPeer, + bool videoStream) { + const auto badge = videoStream + ? nullptr + : container->add( + object_ptr>( container, - tr::lng_paid_react_top_title(), - [](QRect) { return st::creditsBg3->b; }), - st::boxRowPadding + st::paidReactTopTitleMargin), - style::al_top); - + MakeBoostFeaturesBadge( + container, + tr::lng_paid_react_top_title(), + [](QRect) { return st::creditsBg3->b; }), + st::boxRowPadding + st::paidReactTopTitleMargin), + style::al_top); const auto height = st::paidReactTopNameSkip + st::normalFont->height; const auto wrap = container->add( object_ptr>( @@ -431,10 +437,14 @@ void FillTopReactors( barePeerId, changeShownPeer); }; if (list.empty()) { - badge->hide(anim::type::normal); + if (badge) { + badge->hide(anim::type::normal); + } wrap->hide(anim::type::normal); } else { - badge->show(anim::type::normal); + if (badge) { + badge->show(anim::type::normal); + } for (const auto &widget : state->widgets) { widget->hide(); } @@ -448,7 +458,11 @@ void FillTopReactors( const auto i = state->cache.find(key); const auto widget = (i != end(state->cache)) ? i->second - : MakeTopReactor(parent, entry, selectShownPeer); + : MakeTopReactor( + parent, + entry, + selectShownPeer, + videoStream); state->widgets.push_back(widget); widget->show(); } @@ -462,7 +476,9 @@ void FillTopReactors( state->updated.fire({}); }, wrap->lifetime()); - badge->finishAnimating(); + if (badge) { + badge->finishAnimating(); + } wrap->finishAnimating(); rpl::combine( @@ -509,7 +525,9 @@ void PaidReactionsBox( state->chosen = count; }; - const auto videoStream = args.videoStreamChoosing; + const auto videoStreamChoosing = args.videoStreamChoosing; + const auto videoStreamSending = args.videoStreamSending; + const auto videoStream = videoStreamChoosing || videoStreamSending; const auto initialShownPeer = ranges::find( args.top, true, @@ -552,7 +570,7 @@ void PaidReactionsBox( nullptr, &st::paidReactBubbleIcon, st::boxRowPadding); - if (args.videoStreamChoosing) { + if (videoStream) { state->chosen.value() | rpl::start_with_next([=](int count) { bubble->setBrushOverride(activeFgOverride(count)); }, bubble->lifetime()); @@ -568,24 +586,40 @@ void PaidReactionsBox( args.chosen, args.max, changed, - args.videoStreamChoosing ? activeFgOverride : Fn()); + videoStream ? activeFgOverride : Fn()); box->addTopButton( dark ? st::darkEditStarsClose : st::boxTitleClose, [=] { box->closeBox(); }); - if (args.videoStreamChoosing) { + const auto addTopReactors = [&] { + FillTopReactors( + content, + std::move(args.top), + state->chosen.value(), + state->shownPeer.value(), + [=](uint64 barePeerId) { + state->shownPeer = state->savedShownPeer = barePeerId; + }, + videoStream); + }; + + if (videoStreamChoosing) { using namespace Calls::Group::Ui; box->addRow( VideoStreamStarsLevel(box, state->chosen.value()), st::boxRowPadding + QMargins(0, st::paidReactTitleSkip, 0, 0)); + } else if (videoStreamSending) { + addTopReactors(); } box->addRow( object_ptr( box, - (videoStream + (videoStreamChoosing ? tr::lng_paid_comment_title() + : videoStreamSending + ? tr::lng_paid_reaction_title() : tr::lng_paid_react_title()), dark ? st::darkEditStarsCenteredTitle : st::boostCenteredTitle), st::boxRowPadding + QMargins(0, st::paidReactTitleSkip, 0, 0), @@ -597,10 +631,12 @@ void PaidReactionsBox( const auto label = CreateChild( labelWrap, (videoStream - ? tr::lng_paid_comment_about( - lt_name, - rpl::single(Text::Bold(args.name)), - Text::RichLangValue) + ? (videoStreamChoosing + ? tr::lng_paid_comment_about + : tr::lng_paid_reaction_about)( + lt_name, + rpl::single(Text::Bold(args.name)), + Text::RichLangValue) : already ? tr::lng_paid_react_already( lt_count, @@ -622,21 +658,17 @@ void PaidReactionsBox( label->moveToLeft(0, skip); }, label->lifetime()); - if (!args.videoStreamChoosing) { - FillTopReactors( - content, - std::move(args.top), - state->chosen.value(), - state->shownPeer.value(), - [=](uint64 barePeerId) { - state->shownPeer = state->savedShownPeer = barePeerId; - }); + if (!videoStream) { + addTopReactors(); + + const auto skip = st::defaultCheckbox.margin.bottom(); const auto named = box->addRow( object_ptr( box, tr::lng_paid_react_show_in_top(tr::now), state->shownPeer.current() != 0, st::paidReactBoxCheckbox), + st::boxRowPadding + QMargins(0, 0, 0, skip), style::al_top); named->checkedValue( ) | rpl::start_with_next([=](bool show) { diff --git a/Telegram/SourceFiles/payments/ui/payments_reaction_box.h b/Telegram/SourceFiles/payments/ui/payments_reaction_box.h index 3c38f7b1c8..36f9e26f6e 100644 --- a/Telegram/SourceFiles/payments/ui/payments_reaction_box.h +++ b/Telegram/SourceFiles/payments/ui/payments_reaction_box.h @@ -44,6 +44,7 @@ struct PaidReactionBoxArgs { rpl::producer balanceValue; Fn send; bool videoStreamChoosing = false; + bool videoStreamSending = false; bool dark = false; };