mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Send stars from star reaction right click.
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user