Send stars from star reaction right click.

This commit is contained in:
John Preston
2025-10-28 12:33:23 +04:00
parent 1da47f62fc
commit 8eb0ec9f7a
11 changed files with 170 additions and 74 deletions
+3
View File
@@ -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+";
@@ -34,15 +34,18 @@ void VideoStreamStarsBox(
VideoStreamStarsBoxArgs &&args) {
args.show->session().credits().load();
const auto sending = args.sending;
auto submitText = [=](rpl::producer<int> 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<uint64>();
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<PeerData*> 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,
});
}
@@ -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<ChatHelpers::Show> show;
std::vector<Data::MessageReactionsTopPaid> top;
int current = 0;
bool sending = false;
Fn<void(int)> save;
QString name;
};
@@ -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<std::vector<StarReactionTop>> top) {
_starsReactionTop = std::move(top);
}
auto ComposeControls::starsReactionIncrements() const
-> rpl::producer<StarReactionIncrement> {
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());
@@ -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<Ui::SendStarButtonState> count);
[[nodiscard]] rpl::producer<> starsReactionIncrements() const;
using StarReactionTop = Data::MessageReactionsTopPaid;
void setStarsReactionTop(
rpl::producer<std::vector<StarReactionTop>> top);
struct StarReactionIncrement {
int count = 0;
bool fromBox = false;
};
[[nodiscard]] auto starsReactionIncrements() const
-> rpl::producer<StarReactionIncrement>;
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<StarReactionIncrement> _starsReactionIncrements;
rpl::variable<std::vector<StarReactionTop>> _starsReactionTop;
rpl::variable<bool> _recording;
rpl::variable<bool> _hasSendText;
@@ -1771,13 +1771,13 @@ auto Controller::starsReactionsValue() const
});
}
void Controller::setStarsReactionIncrements(rpl::producer<> increments) {
void Controller::setStarsReactionIncrements(rpl::producer<int> 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);
}
@@ -186,7 +186,7 @@ public:
void setCommentsShownToggles(rpl::producer<> toggles);
[[nodiscard]] auto starsReactionsValue() const
-> rpl::producer<Ui::SendStarButtonState>;
void setStarsReactionIncrements(rpl::producer<> increments);
void setStarsReactionIncrements(rpl::producer<int> increments);
void unfocusReply();
void shareRequested();
@@ -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<Calls::GroupCall*> 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<Data::MessageReactionsTopPaid>();
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() {
@@ -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 },
@@ -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<RpWidget*> widget) {
@@ -254,7 +257,8 @@ void AddArrowDown(not_null<RpWidget*> widget) {
[[nodiscard]] not_null<RpWidget*> MakeTopReactor(
not_null<QWidget*> parent,
const PaidReactionTop &data,
Fn<void()> selectShownPeer) {
Fn<void()> selectShownPeer,
bool videoStream) {
const auto result = CreateChild<AbstractButton>(parent);
result->show();
if (data.click && !data.my) {
@@ -288,7 +292,7 @@ void AddArrowDown(not_null<RpWidget*> 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<RpWidget*> 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<PaidReactionTop> top,
rpl::producer<int> chosen,
rpl::producer<uint64> shownPeer,
Fn<void(uint64)> changeShownPeer) {
const auto badge = container->add(
object_ptr<SlideWrap<RpWidget>>(
container,
MakeBoostFeaturesBadge(
Fn<void(uint64)> changeShownPeer,
bool videoStream) {
const auto badge = videoStream
? nullptr
: container->add(
object_ptr<SlideWrap<RpWidget>>(
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<SlideWrap<FixedHeightWidget>>(
@@ -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<QColor(int)>());
videoStream ? activeFgOverride : Fn<QColor(int)>());
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<FlatLabel>(
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<FlatLabel>(
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<Checkbox>(
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) {
@@ -44,6 +44,7 @@ struct PaidReactionBoxArgs {
rpl::producer<CreditsAmount> balanceValue;
Fn<void(int, uint64)> send;
bool videoStreamChoosing = false;
bool videoStreamSending = false;
bool dark = false;
};