mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-13 04:37:11 +02:00
Send paid reactions from channels.
This commit is contained in:
parent
79ce24222a
commit
ba84499f00
6 changed files with 175 additions and 38 deletions
|
@ -73,6 +73,17 @@ const std::vector<SendAsPeer> &SendAsPeers::list(
|
|||
return (i != end(_lists)) ? i->second : _onlyMe;
|
||||
}
|
||||
|
||||
std::vector<not_null<PeerData*>> SendAsPeers::paidReactionList() const {
|
||||
auto result = std::vector<not_null<PeerData*>>();
|
||||
const auto owner = &_session->data();
|
||||
owner->enumerateBroadcasts([&](not_null<ChannelData*> channel) {
|
||||
if (channel->amCreator() && !ranges::contains(result, channel)) {
|
||||
result.push_back(channel);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
rpl::producer<not_null<PeerData*>> SendAsPeers::updated() const {
|
||||
return _updates.events();
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ public:
|
|||
void setChosen(not_null<PeerData*> peer, PeerId chosenId);
|
||||
[[nodiscard]] PeerId chosen(not_null<PeerData*> peer) const;
|
||||
|
||||
[[nodiscard]] std::vector<not_null<PeerData*>> paidReactionList() const;
|
||||
|
||||
// If !list(peer).empty() then the result will be from that list.
|
||||
[[nodiscard]] not_null<PeerData*> resolveChosen(
|
||||
not_null<PeerData*> peer) const;
|
||||
|
|
|
@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history_item.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/session/session_show.h"
|
||||
#include "main/session/send_as_peers.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "payments/ui/payments_reaction_box.h"
|
||||
|
@ -206,35 +207,46 @@ void ShowPaidReactionDetails(
|
|||
.photo = (peer
|
||||
? Ui::MakeUserpicThumbnail(peer)
|
||||
: Ui::MakeHiddenAuthorThumbnail()),
|
||||
.barePeerId = peer ? uint64(peer->id.value) : 0,
|
||||
.count = int(entry.count),
|
||||
.click = peer ? open : Fn<void()>(),
|
||||
.my = (entry.my == 1),
|
||||
});
|
||||
};
|
||||
const auto channels = session->sendAsPeers().paidReactionList();
|
||||
const auto topPaid = item->topPaidReactionsWithLocal();
|
||||
top.reserve(topPaid.size() + 2);
|
||||
top.reserve(topPaid.size() + 2 + channels.size());
|
||||
for (const auto &entry : topPaid) {
|
||||
add(entry);
|
||||
if (entry.my) {
|
||||
auto copy = entry;
|
||||
copy.peer = entry.peer ? nullptr : session->user().get();
|
||||
add(copy);
|
||||
}
|
||||
}
|
||||
if (!ranges::contains(top, true, &Ui::PaidReactionTop::my)) {
|
||||
auto entry = Data::MessageReactionsTopPaid{
|
||||
.peer = session->user(),
|
||||
.count = 0,
|
||||
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;
|
||||
if (!myAdded.emplace(barePeerId).second) {
|
||||
return;
|
||||
}
|
||||
add(Data::MessageReactionsTopPaid{
|
||||
.peer = peer,
|
||||
.count = myCount,
|
||||
.my = true,
|
||||
};
|
||||
add(entry);
|
||||
entry.peer = nullptr;
|
||||
add(entry);
|
||||
if (session->api().globalPrivacy().paidReactionShownPeerCurrent()) {
|
||||
std::swap(top.front(), top.back());
|
||||
}
|
||||
});
|
||||
};
|
||||
const auto globalPrivacy = &session->api().globalPrivacy();
|
||||
const auto shown = globalPrivacy->paidReactionShownPeerCurrent();
|
||||
const auto owner = &session->data();
|
||||
const auto shownPeer = shown ? owner->peer(shown).get() : nullptr;
|
||||
myAdd(shownPeer);
|
||||
myAdd(session->user());
|
||||
myAdd(nullptr);
|
||||
for (const auto &channel : channels) {
|
||||
myAdd(channel);
|
||||
}
|
||||
ranges::sort(top, ranges::greater(), &Ui::PaidReactionTop::count);
|
||||
ranges::stable_sort(top, ranges::greater(), &Ui::PaidReactionTop::count);
|
||||
|
||||
const auto linked = item->discussionPostOriginalSender();
|
||||
const auto channel = (linked ? linked : item->history()->peer.get());
|
||||
|
@ -245,8 +257,8 @@ void ShowPaidReactionDetails(
|
|||
.channel = channel->name(),
|
||||
.submit = std::move(submitText),
|
||||
.balanceValue = session->credits().balanceValue(),
|
||||
.send = [=](int count, bool anonymous) {
|
||||
send(count, anonymous ? PeerId() : 0, send);
|
||||
.send = [=](int count, uint64 barePeerId) {
|
||||
send(count, PeerId(barePeerId), send);
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
|
@ -10,19 +10,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "base/qt/qt_compare.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/boxes/boost_box.h" // MakeBoostFeaturesBadge.
|
||||
#include "ui/controls/who_reacted_context_action.h"
|
||||
#include "ui/effects/premium_bubble.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/continuous_sliders.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/dynamic_image.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_media_player.h"
|
||||
#include "styles/style_premium.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
|
@ -192,13 +196,41 @@ void PaidReactionSlider(
|
|||
return result;
|
||||
}
|
||||
|
||||
void AddArrowDown(not_null<RpWidget*> widget) {
|
||||
const auto arrow = CreateChild<RpWidget>(widget);
|
||||
const auto icon = &st::paidReactChannelArrow;
|
||||
const auto skip = st::lineWidth * 4;
|
||||
const auto size = icon->width() + skip * 2;
|
||||
arrow->resize(size, size);
|
||||
widget->widthValue() | rpl::start_with_next([=](int width) {
|
||||
const auto left = (width - st::paidReactTopUserpic) / 2;
|
||||
arrow->moveToRight(left - skip, -st::lineWidth, width);
|
||||
}, widget->lifetime());
|
||||
arrow->paintRequest() | rpl::start_with_next([=] {
|
||||
Painter p(arrow);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setBrush(st::activeButtonBg);
|
||||
p.setPen(st::activeButtonFg);
|
||||
const auto rect = arrow->rect();
|
||||
const auto line = st::lineWidth;
|
||||
p.drawEllipse(rect.marginsRemoved({ line, line, line, line }));
|
||||
icon->paint(p, skip, (size - icon->height()) / 2 + line, size);
|
||||
}, widget->lifetime());
|
||||
arrow->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
arrow->show();
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<RpWidget*> MakeTopReactor(
|
||||
not_null<QWidget*> parent,
|
||||
const PaidReactionTop &data) {
|
||||
const PaidReactionTop &data,
|
||||
Fn<void()> selectShownPeer) {
|
||||
const auto result = CreateChild<AbstractButton>(parent);
|
||||
result->show();
|
||||
if (data.click && !data.my) {
|
||||
result->setClickedCallback(data.click);
|
||||
} else if (data.click && selectShownPeer) {
|
||||
result->setClickedCallback(selectShownPeer);
|
||||
AddArrowDown(result);
|
||||
} else {
|
||||
result->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
}
|
||||
|
@ -244,11 +276,60 @@ void PaidReactionSlider(
|
|||
return result;
|
||||
}
|
||||
|
||||
void SelectShownPeer(
|
||||
std::shared_ptr<QPointer<PopupMenu>> menu,
|
||||
not_null<QWidget*> parent,
|
||||
const std::vector<PaidReactionTop> &mine,
|
||||
uint64 selected,
|
||||
Fn<void(uint64)> callback) {
|
||||
if (*menu) {
|
||||
(*menu)->hideMenu();
|
||||
}
|
||||
(*menu) = CreateChild<PopupMenu>(
|
||||
parent,
|
||||
st::paidReactChannelMenu);
|
||||
|
||||
struct Entry {
|
||||
not_null<Ui::WhoReactedEntryAction*> action;
|
||||
std::shared_ptr<Ui::DynamicImage> userpic;
|
||||
};
|
||||
auto actions = std::make_shared<std::vector<Entry>>();
|
||||
actions->reserve(mine.size());
|
||||
for (const auto &entry : mine) {
|
||||
auto action = base::make_unique_q<WhoReactedEntryAction>(
|
||||
(*menu)->menu(),
|
||||
nullptr,
|
||||
(*menu)->menu()->st(),
|
||||
Ui::WhoReactedEntryData());
|
||||
const auto index = int(actions->size());
|
||||
actions->push_back({ action.get(), entry.photo->clone() });
|
||||
const auto id = entry.barePeerId;
|
||||
const auto updateUserpic = [=] {
|
||||
const auto size = st::defaultWhoRead.photoSize;
|
||||
actions->at(index).action->setData({
|
||||
.text = entry.name,
|
||||
.type = ((id == selected)
|
||||
? Ui::WhoReactedType::RefRecipientNow
|
||||
: Ui::WhoReactedType::RefRecipient),
|
||||
.userpic = actions->at(index).userpic->image(size),
|
||||
.callback = [=] { callback(id); },
|
||||
});
|
||||
};
|
||||
actions->back().userpic->subscribeToUpdates(updateUserpic);
|
||||
|
||||
(*menu)->addAction(std::move(action));
|
||||
updateUserpic();
|
||||
}
|
||||
(*menu)->popup(QCursor::pos());
|
||||
|
||||
}
|
||||
|
||||
void FillTopReactors(
|
||||
not_null<VerticalLayout*> container,
|
||||
std::vector<PaidReactionTop> top,
|
||||
rpl::producer<int> chosen,
|
||||
rpl::producer<bool> anonymous) {
|
||||
rpl::producer<uint64> shownPeer,
|
||||
Fn<void(uint64)> changeShownPeer) {
|
||||
container->add(
|
||||
MakeBoostFeaturesBadge(
|
||||
container,
|
||||
|
@ -272,28 +353,33 @@ void FillTopReactors(
|
|||
bool chosenChanged = false;
|
||||
};
|
||||
const auto state = wrap->lifetime().make_state<State>();
|
||||
const auto menu = std::make_shared<QPointer<Ui::PopupMenu>>();
|
||||
|
||||
rpl::combine(
|
||||
std::move(chosen),
|
||||
std::move(anonymous)
|
||||
) | rpl::start_with_next([=](int chosen, bool anonymous) {
|
||||
std::move(shownPeer)
|
||||
) | rpl::start_with_next([=](int chosen, uint64 barePeerId) {
|
||||
if (!state->initialChosen) {
|
||||
state->initialChosen = chosen;
|
||||
} else if (*state->initialChosen != chosen) {
|
||||
state->chosenChanged = true;
|
||||
}
|
||||
auto mine = std::vector<PaidReactionTop>();
|
||||
auto list = std::vector<PaidReactionTop>();
|
||||
list.reserve(kMaxTopPaidShown + 1);
|
||||
for (const auto &entry : top) {
|
||||
if (!entry.my) {
|
||||
list.push_back(entry);
|
||||
} else if (!entry.click == anonymous) {
|
||||
} else if (entry.barePeerId == barePeerId) {
|
||||
auto copy = entry;
|
||||
if (state->chosenChanged) {
|
||||
copy.count += chosen;
|
||||
}
|
||||
list.push_back(copy);
|
||||
}
|
||||
if (entry.my && entry.barePeerId) {
|
||||
mine.push_back(entry);
|
||||
}
|
||||
}
|
||||
ranges::stable_sort(
|
||||
list,
|
||||
|
@ -303,6 +389,14 @@ void FillTopReactors(
|
|||
|| (!list.empty() && !list.back().count)) {
|
||||
list.pop_back();
|
||||
}
|
||||
auto selectShownPeer = (mine.size() < 2)
|
||||
? Fn<void()>()
|
||||
: [=] { SelectShownPeer(
|
||||
menu,
|
||||
parent,
|
||||
mine,
|
||||
barePeerId,
|
||||
changeShownPeer); };
|
||||
if (list.empty()) {
|
||||
wrap->hide(anim::type::normal);
|
||||
} else {
|
||||
|
@ -319,7 +413,7 @@ void FillTopReactors(
|
|||
const auto i = state->cache.find(key);
|
||||
const auto widget = (i != end(state->cache))
|
||||
? i->second
|
||||
: MakeTopReactor(parent, entry);
|
||||
: MakeTopReactor(parent, entry, selectShownPeer);
|
||||
state->widgets.push_back(widget);
|
||||
widget->show();
|
||||
}
|
||||
|
@ -368,7 +462,8 @@ void PaidReactionsBox(
|
|||
|
||||
struct State {
|
||||
rpl::variable<int> chosen;
|
||||
rpl::variable<bool> anonymous;
|
||||
rpl::variable<uint64> shownPeer;
|
||||
uint64 savedShownPeer = 0;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
|
||||
|
@ -377,12 +472,16 @@ void PaidReactionsBox(
|
|||
state->chosen = count;
|
||||
};
|
||||
|
||||
const auto initialAnonymous = ranges::find(
|
||||
const auto initialShownPeer = ranges::find(
|
||||
args.top,
|
||||
true,
|
||||
&PaidReactionTop::my
|
||||
)->click == nullptr;
|
||||
state->anonymous = initialAnonymous;
|
||||
)->barePeerId;
|
||||
state->shownPeer = initialShownPeer;
|
||||
state->savedShownPeer = ranges::find_if(args.top, [](
|
||||
const PaidReactionTop &entry) {
|
||||
return entry.my && entry.barePeerId != 0;
|
||||
})->barePeerId;
|
||||
|
||||
const auto content = box->verticalLayout();
|
||||
AddSkip(content, st::boxTitleClose.height + st::paidReactBubbleTop);
|
||||
|
@ -455,25 +554,30 @@ void PaidReactionsBox(
|
|||
content,
|
||||
std::move(args.top),
|
||||
state->chosen.value(),
|
||||
state->anonymous.value());
|
||||
state->shownPeer.value(),
|
||||
[=](uint64 barePeerId) {
|
||||
state->shownPeer = state->savedShownPeer = barePeerId;
|
||||
});
|
||||
|
||||
const auto named = box->addRow(object_ptr<CenterWrap<Checkbox>>(
|
||||
box,
|
||||
object_ptr<Checkbox>(
|
||||
box,
|
||||
tr::lng_paid_react_show_in_top(tr::now),
|
||||
!state->anonymous.current())));
|
||||
state->anonymous = named->entity()->checkedValue(
|
||||
) | rpl::map(!rpl::mappers::_1);
|
||||
state->shownPeer.current() != 0)));
|
||||
named->entity()->checkedValue(
|
||||
) | rpl::start_with_next([=](bool show) {
|
||||
state->shownPeer = show ? state->savedShownPeer : 0;
|
||||
}, named->lifetime());
|
||||
|
||||
const auto button = box->addButton(rpl::single(QString()), [=] {
|
||||
args.send(state->chosen.current(), !named->entity()->checked());
|
||||
args.send(state->chosen.current(), state->shownPeer.current());
|
||||
});
|
||||
|
||||
box->boxClosing() | rpl::filter([=] {
|
||||
return state->anonymous.current() != initialAnonymous;
|
||||
return state->shownPeer.current() != initialShownPeer;
|
||||
}) | rpl::start_with_next([=] {
|
||||
args.send(0, state->anonymous.current());
|
||||
args.send(0, state->shownPeer.current());
|
||||
}, box->lifetime());
|
||||
|
||||
{
|
||||
|
|
|
@ -23,6 +23,7 @@ struct TextWithContext {
|
|||
struct PaidReactionTop {
|
||||
QString name;
|
||||
std::shared_ptr<DynamicImage> photo;
|
||||
uint64 barePeerId = 0;
|
||||
int count = 0;
|
||||
Fn<void()> click;
|
||||
bool my = false;
|
||||
|
@ -37,7 +38,7 @@ struct PaidReactionBoxArgs {
|
|||
QString channel;
|
||||
Fn<rpl::producer<TextWithContext>(rpl::producer<int> amount)> submit;
|
||||
rpl::producer<StarsAmount> balanceValue;
|
||||
Fn<void(int, bool)> send;
|
||||
Fn<void(int, uint64)> send;
|
||||
};
|
||||
|
||||
void PaidReactionsBox(
|
||||
|
|
|
@ -401,6 +401,13 @@ paidReactToastLabel: FlatLabel(defaultFlatLabel) {
|
|||
paidReactTopStarIcon: icon{{ "chat/mini_stars", premiumButtonFg }};
|
||||
paidReactTopStarIconPosition: point(0px, 1px);
|
||||
paidReactTopStarSkip: 4px;
|
||||
paidReactChannelArrow: icon{{ "intro_country_dropdown", activeButtonFg }};
|
||||
paidReactChannelMenu: PopupMenu(popupMenuWithIcons) {
|
||||
menu: Menu(menuWithIcons) {
|
||||
widthMax: 240px;
|
||||
}
|
||||
maxHeight: 345px;
|
||||
}
|
||||
|
||||
toastUndoStroke: 2px;
|
||||
toastUndoSpace: 8px;
|
||||
|
|
Loading…
Add table
Reference in a new issue