Allow changing the recipients.

This commit is contained in:
John Preston 2024-12-02 20:20:34 +04:00
parent 82cec83d87
commit b8bf3f6520
14 changed files with 386 additions and 56 deletions

View file

@ -1404,10 +1404,6 @@ void Session::forgetPassportCredentials() {
_passportCredentials = nullptr; _passportCredentials = nullptr;
} }
QString Session::nameSortKey(const QString &name) const {
return TextUtilities::RemoveAccents(name).toLower();
}
void Session::setupMigrationViewer() { void Session::setupMigrationViewer() {
session().changes().peerUpdates( session().changes().peerUpdates(
PeerUpdate::Flag::Migration PeerUpdate::Flag::Migration

View file

@ -115,8 +115,6 @@ public:
return *_session; return *_session;
} }
[[nodiscard]] QString nameSortKey(const QString &name) const;
[[nodiscard]] Groups &groups() { [[nodiscard]] Groups &groups() {
return _groups; return _groups;
} }

View file

@ -98,7 +98,7 @@ History::History(not_null<Data::Session*> owner, PeerId peerId)
: Thread(owner, Type::History) : Thread(owner, Type::History)
, peer(owner->peer(peerId)) , peer(owner->peer(peerId))
, _delegateMixin(HistoryInner::DelegateMixin()) , _delegateMixin(HistoryInner::DelegateMixin())
, _chatListNameSortKey(owner->nameSortKey(peer->name())) , _chatListNameSortKey(TextUtilities::NameSortKey(peer->name()))
, _sendActionPainter(this) { , _sendActionPainter(this) {
Thread::setMuted(owner->notifySettings().isMuted(peer)); Thread::setMuted(owner->notifySettings().isMuted(peer));
@ -2314,7 +2314,7 @@ const QString &History::chatListNameSortKey() const {
} }
void History::refreshChatListNameSortKey() { void History::refreshChatListNameSortKey() {
_chatListNameSortKey = owner().nameSortKey(peer->name()); _chatListNameSortKey = TextUtilities::NameSortKey(peer->name());
} }
const base::flat_set<QString> &History::chatListNameWords() const { const base::flat_set<QString> &History::chatListNameWords() const {

View file

@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/send_credits_box.h" // Ui::CreditsEmoji. #include "boxes/send_credits_box.h" // Ui::CreditsEmoji.
#include "chat_helpers/stickers_lottie.h" #include "chat_helpers/stickers_lottie.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_channel.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_sticker.h"
@ -21,9 +23,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_common.h" #include "settings/settings_common.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h" #include "ui/controls/userpic_button.h"
#include "ui/controls/who_reacted_context_action.h"
#include "ui/dynamic_image.h"
#include "ui/dynamic_thumbnails.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/widgets/popup_menu.h"
#include "ui/wrap/padding_wrap.h" #include "ui/wrap/padding_wrap.h"
#include "ui/wrap/table_layout.h" #include "ui/wrap/table_layout.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
@ -31,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/vertical_list.h" #include "ui/vertical_list.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_dialogs.h" #include "styles/style_dialogs.h"
#include "styles/style_giveaway.h" #include "styles/style_giveaway.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
@ -156,6 +163,58 @@ void ConnectStarRef(
return result; return result;
} }
void ChooseRecipient(
not_null<Ui::RpWidget*> button,
const std::vector<not_null<PeerData*>> &list,
not_null<PeerData*> now,
Fn<void(not_null<PeerData*>)> done) {
const auto menu = Ui::CreateChild<Ui::PopupMenu>(
button,
st::starrefPopupMenu);
struct Entry {
not_null<Ui::WhoReactedEntryAction*> action;
std::shared_ptr<Ui::DynamicImage> userpic;
};
auto actions = std::make_shared<std::vector<Entry>>();
actions->reserve(list.size());
for (const auto &peer : list) {
auto view = peer->createUserpicView();
auto action = base::make_unique_q<Ui::WhoReactedEntryAction>(
menu->menu(),
Data::ReactedMenuFactory(&list.front()->session()),
menu->menu()->st(),
Ui::WhoReactedEntryData());
const auto index = int(actions->size());
actions->push_back({ action.get(), Ui::MakeUserpicThumbnail(peer) });
const auto updateUserpic = [=] {
const auto size = st::defaultWhoRead.photoSize;
actions->at(index).action->setData({
.text = peer->name(),
.date = (peer->isSelf()
? tr::lng_group_call_join_as_personal(tr::now)
: peer->isUser()
? tr::lng_status_bot(tr::now)
: peer->isBroadcast()
? tr::lng_channel_status(tr::now)
: tr::lng_group_status(tr::now)),
.type = (peer == now
? Ui::WhoReactedType::RefRecipientNow
: Ui::WhoReactedType::RefRecipient),
.userpic = actions->at(index).userpic->image(size),
.callback = [=] { done(peer); },
});
};
actions->back().userpic->subscribeToUpdates(updateUserpic);
menu->addAction(std::move(action));
updateUserpic();
}
menu->popup(button->mapToGlobal(QPoint(button->width() / 2, 0)));
}
} // namespace } // namespace
QString FormatCommission(ushort commission) { QString FormatCommission(ushort commission) {
@ -453,9 +512,10 @@ object_ptr<Ui::BoxContent> StarRefLinkBox(
}); });
} }
[[nodiscard]] object_ptr<Ui::BoxContent> JoinStarRefBox( object_ptr<Ui::BoxContent> JoinStarRefBox(
ConnectedBot row, ConnectedBot row,
not_null<PeerData*> peer, not_null<PeerData*> initialRecipient,
std::vector<not_null<PeerData*>> recipients,
Fn<void(ConnectedBotState)> done) { Fn<void(ConnectedBotState)> done) {
Expects(row.bot->isUser()); Expects(row.bot->isUser());
@ -464,6 +524,13 @@ object_ptr<Ui::BoxContent> StarRefLinkBox(
const auto bot = row.bot; const auto bot = row.bot;
const auto program = row.state.program; const auto program = row.state.program;
auto list = recipients;
if (!list.empty()) {
list.erase(ranges::remove(list, bot), end(list));
if (!ranges::contains(list, initialRecipient)) {
list.insert(begin(list), initialRecipient);
}
}
box->setStyle(st::starrefFooterBox); box->setStyle(st::starrefFooterBox);
box->setNoContentMargin(true); box->setNoContentMargin(true);
@ -471,13 +538,34 @@ object_ptr<Ui::BoxContent> StarRefLinkBox(
box->closeBox(); box->closeBox();
}); });
box->addRow( struct State {
CreateUserpicsTransfer( rpl::variable<not_null<PeerData*>> recipient;
box, QPointer<Ui::GenericBox> weak;
rpl::single(std::vector{ not_null<PeerData*>(bot) }), bool sent = false;
peer, };
UserpicsTransferType::StarRefJoin), const auto state = std::make_shared<State>(State{
st::boxRowPadding + st::starrefJoinUserpicsPadding); .recipient = initialRecipient,
.weak = box.get(),
});
const auto userpicsWrap = box->addRow(
object_ptr<Ui::VerticalLayout>(box),
QMargins());
state->recipient.value(
) | rpl::start_with_next([=](not_null<PeerData*> recipient) {
while (userpicsWrap->count()) {
delete userpicsWrap->widgetAt(0);
}
userpicsWrap->add(
CreateUserpicsTransfer(
box,
rpl::single(std::vector{ not_null<PeerData*>(bot) }),
recipient,
UserpicsTransferType::StarRefJoin),
st::boxRowPadding + st::starrefJoinUserpicsPadding);
userpicsWrap->resizeToWidth(box->width());
}, box->lifetime());
box->addRow( box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>( object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box, box,
@ -504,7 +592,7 @@ object_ptr<Ui::BoxContent> StarRefLinkBox(
Ui::AddSkip(box->verticalLayout(), st::defaultVerticalListSkip * 3); Ui::AddSkip(box->verticalLayout(), st::defaultVerticalListSkip * 3);
if (const auto average = program.revenuePerUser) { if (const auto average = program.revenuePerUser) {
const auto layout = box->verticalLayout(); const auto layout = box->verticalLayout();
const auto session = &peer->session(); const auto session = &initialRecipient->session();
const auto makeContext = [session](Fn<void()> update) { const auto makeContext = [session](Fn<void()> update) {
return Core::MarkedTextContext{ return Core::MarkedTextContext{
.session = session, .session = session,
@ -512,13 +600,14 @@ object_ptr<Ui::BoxContent> StarRefLinkBox(
}; };
}; };
auto text = Ui::Text::Colorized(Ui::CreditsEmoji(session)); auto text = Ui::Text::Colorized(Ui::CreditsEmoji(session));
text.append(Lang::FormatStarsAmountDecimal(average)); text.append(Lang::FormatStarsAmountRounded(average));
layout->add( layout->add(
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
box, box,
tr::lng_star_ref_one_daily_revenue( tr::lng_star_ref_one_daily_revenue(
lt_amount, lt_amount,
rpl::single(Ui::Text::Wrapped(text, EntityType::Bold)), rpl::single(
Ui::Text::Wrapped(text, EntityType::Bold)),
Ui::Text::WithEntities), Ui::Text::WithEntities),
st::starrefRevenueText, st::starrefRevenueText,
st::defaultPopupMenu, st::defaultPopupMenu,
@ -526,35 +615,89 @@ object_ptr<Ui::BoxContent> StarRefLinkBox(
st::boxRowPadding); st::boxRowPadding);
Ui::AddSkip(layout, st::defaultVerticalListSkip); Ui::AddSkip(layout, st::defaultVerticalListSkip);
} }
#if 0
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
tr::lng_star_ref_link_recipient(),
st::starrefCenteredText));
Ui::AddSkip(box->verticalLayout());
box->addRow(object_ptr<Ui::AbstractButton>::fromRaw(
MakePeerBubbleButton(box, peer).release()
))->setAttribute(Qt::WA_TransparentForMouseEvents);
#endif
struct State { if (!list.empty()) {
QPointer<Ui::GenericBox> weak; struct Name {
bool sent = false; not_null<PeerData*> peer;
}; QString name;
const auto state = std::make_shared<State>(); };
state->weak = box; auto names = ranges::views::transform(list, [](auto peer) {
const auto name = TextUtilities::NameSortKey(peer->name());
return Name{ peer, name };
}) | ranges::to_vector;
ranges::sort(names, ranges::less(), &Name::name);
list = ranges::views::transform(names, &Name::peer)
| ranges::to_vector;
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
tr::lng_star_ref_link_recipient(),
st::starrefCenteredText));
Ui::AddSkip(box->verticalLayout());
const auto recipientWrap = box->addRow(
object_ptr<Ui::VerticalLayout>(box),
QMargins());
state->recipient.value(
) | rpl::start_with_next([=](not_null<PeerData*> recipient) {
while (recipientWrap->count()) {
delete recipientWrap->widgetAt(0);
}
const auto selectable = (list.size() > 1);
const auto bgOverride = selectable
? &st::lightButtonBgOver
: nullptr;
const auto right = selectable
? Ui::CreateChild<Ui::RpWidget>(recipientWrap)
: nullptr;
if (right) {
const auto skip = st::chatGiveawayPeerPadding.right();
const auto icon = &st::starrefRecipientArrow;
right->resize(skip + icon->width(), icon->height());
right->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(right);
icon->paint(p, skip, 0, right->width());
}, right->lifetime());
}
const auto button = recipientWrap->add(
object_ptr<Ui::AbstractButton>::fromRaw(
MakePeerBubbleButton(
box,
recipient,
right,
bgOverride).release()),
st::boxRowPadding);
recipientWrap->resizeToWidth(box->width());
if (!selectable) {
button->setAttribute(Qt::WA_TransparentForMouseEvents);
return;
}
button->setClickedCallback([=] {
const auto callback = [=](not_null<PeerData*> peer) {
state->recipient = peer;
};
ChooseRecipient(
button,
list,
state->recipient.current(),
crl::guard(button, callback));
});
}, box->lifetime());
}
const auto send = [=] { const auto send = [=] {
if (state->sent) { if (state->sent) {
return; return;
} }
state->sent = true; state->sent = true;
ConnectStarRef(bot->asUser(), peer, [=](ConnectedBot info) { const auto recipient = state->recipient.current();
ConnectStarRef(bot->asUser(), recipient, [=](ConnectedBot info) {
if (const auto onstack = done) { if (const auto onstack = done) {
onstack(info.state); onstack(info.state);
} }
show->show(StarRefLinkBox(info, peer)); show->show(StarRefLinkBox(info, recipient));
if (const auto strong = state->weak.data()) { if (const auto strong = state->weak.data()) {
strong->closeBox(); strong->closeBox();
} }
@ -624,18 +767,103 @@ object_ptr<Ui::BoxContent> ConfirmEndBox(Fn<void()> finish) {
}); });
} }
void ResolveRecipients(
not_null<Main::Session*> session,
Fn<void(std::vector<not_null<PeerData*>>)> done) {
struct State {
not_null<Main::Session*> session;
std::vector<not_null<PeerData*>> list;
Fn<void(std::vector<not_null<PeerData*>>)> done;
};
const auto state = std::make_shared<State>(State{
.session = session,
.done = std::move(done),
});
const auto finish1 = [state](const MTPmessages_Chats &result) {
const auto already = int(state->list.size());
const auto session = state->session;
result.match([&](const auto &data) {
const auto &list = data.vchats().v;
state->list.reserve(list.size() + (already ? already : 1));
if (!already) {
state->list.push_back(session->user());
}
for (const auto &chat : list) {
const auto peer = session->data().processChat(chat);
if (const auto channel = peer->asBroadcast()) {
if (channel->canPostMessages()) {
state->list.push_back(channel);
}
}
}
if (already) {
base::take(state->done)(base::take(state->list));
}
});
};
const auto finish2 = [state](const MTPVector<MTPUser> &result) {
const auto already = int(state->list.size());
const auto session = state->session;
const auto &list = result.v;
state->list.reserve(list.size() + (already ? already : 1));
if (!already) {
state->list.push_back(session->user());
}
for (const auto &user : list) {
state->list.push_back(session->data().processUser(user));
}
if (already) {
base::take(state->done)(base::take(state->list));
}
};
session->api().request(MTPchannels_GetAdminedPublicChannels(
MTP_flags(0)
)).done(finish1).fail([=] {
finish1(MTP_messages_chats(MTP_vector<MTPChat>(0)));
}).send();
state->session->api().request(MTPbots_GetAdminedBots(
)).done(finish2).fail([=] {
finish2(MTP_vector<MTPUser>(0));
}).send();
}
std::unique_ptr<Ui::AbstractButton> MakePeerBubbleButton( std::unique_ptr<Ui::AbstractButton> MakePeerBubbleButton(
not_null<QWidget*> parent, not_null<QWidget*> parent,
not_null<PeerData*> peer, not_null<PeerData*> peer,
Ui::RpWidget *right) { Ui::RpWidget *right,
auto result = std::make_unique<Ui::AbstractButton>(parent); const style::color *bgOverride) {
class Button final : public Ui::AbstractButton {
public:
Button(QWidget *parent, not_null<int*> innerWidth)
: AbstractButton(parent)
, _innerWidth(innerWidth) {
}
void mouseMoveEvent(QMouseEvent *e) override {
const auto inner = *_innerWidth;
const auto skip = (width() - inner) / 2;
const auto p = e->pos();
const auto over = QRect(skip, 0, inner, height()).contains(p);
setOver(over, StateChangeSource::ByHover);
}
private:
const not_null<int*> _innerWidth;
};
auto ownedWidth = std::make_unique<int>();
const auto width = ownedWidth.get();
auto result = std::make_unique<Button>(parent, width);
result->lifetime().add([moved = std::move(ownedWidth)] {});
const auto size = st::chatGiveawayPeerSize; const auto size = st::chatGiveawayPeerSize;
const auto padding = st::chatGiveawayPeerPadding; const auto padding = st::chatGiveawayPeerPadding;
const auto raw = result.get(); const auto raw = result.get();
const auto width = raw->lifetime().make_state<int>();
const auto name = raw->lifetime().make_state<Ui::FlatLabel>( const auto name = raw->lifetime().make_state<Ui::FlatLabel>(
raw, raw,
rpl::single(peer->name()), rpl::single(peer->name()),
@ -691,7 +919,7 @@ std::unique_ptr<Ui::AbstractButton> MakePeerBubbleButton(
p.setClipRect(left + skip, 0, *width - skip, size); p.setClipRect(left + skip, 0, *width - skip, size);
auto hq = PainterHighQualityEnabler(p); auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.setBrush(st::windowBgOver); p.setBrush(bgOverride ? *bgOverride : st::windowBgOver);
p.drawRoundedRect(left, 0, *width, size, skip, skip); p.drawRoundedRect(left, 0, *width, size, skip, skip);
}, raw->lifetime()); }, raw->lifetime());

View file

@ -23,6 +23,10 @@ namespace style {
struct RoundButton; struct RoundButton;
} // namespace style } // namespace style
namespace Main {
class Session;
} // namespace Main
namespace Info::BotStarRef { namespace Info::BotStarRef {
struct ConnectedBotState { struct ConnectedBotState {
@ -65,14 +69,20 @@ void AddFullWidthButtonFooter(
not_null<PeerData*> peer); not_null<PeerData*> peer);
[[nodiscard]] object_ptr<Ui::BoxContent> JoinStarRefBox( [[nodiscard]] object_ptr<Ui::BoxContent> JoinStarRefBox(
ConnectedBot row, ConnectedBot row,
not_null<PeerData*> peer, not_null<PeerData*> initialRecipient,
std::vector<not_null<PeerData*>> recipients,
Fn<void(ConnectedBotState)> done = nullptr); Fn<void(ConnectedBotState)> done = nullptr);
[[nodiscard]] object_ptr<Ui::BoxContent> ConfirmEndBox(Fn<void()> finish); [[nodiscard]] object_ptr<Ui::BoxContent> ConfirmEndBox(Fn<void()> finish);
void ResolveRecipients(
not_null<Main::Session*> session,
Fn<void(std::vector<not_null<PeerData*>>)> done);
std::unique_ptr<Ui::AbstractButton> MakePeerBubbleButton( std::unique_ptr<Ui::AbstractButton> MakePeerBubbleButton(
not_null<QWidget*> parent, not_null<QWidget*> parent,
not_null<PeerData*> peer, not_null<PeerData*> peer,
Ui::RpWidget *right = nullptr); Ui::RpWidget *right = nullptr,
const style::color *bgOverride = nullptr);
void ConfirmUpdate( void ConfirmUpdate(
std::shared_ptr<Ui::Show> show, std::shared_ptr<Ui::Show> show,

View file

@ -87,6 +87,7 @@ public:
private: private:
[[nodiscard]] std::unique_ptr<PeerListRow> createRow(ConnectedBot bot); [[nodiscard]] std::unique_ptr<PeerListRow> createRow(ConnectedBot bot);
void open(not_null<UserData*> bot, ConnectedBotState state); void open(not_null<UserData*> bot, ConnectedBotState state);
void requestRecipients();
void setupAddForBot(); void setupAddForBot();
const not_null<Window::SessionController*> _controller; const not_null<Window::SessionController*> _controller;
@ -97,6 +98,8 @@ private:
base::flat_set<not_null<PeerData*>> _resolving; base::flat_set<not_null<PeerData*>> _resolving;
UserData *_openOnResolve = nullptr; UserData *_openOnResolve = nullptr;
Fn<void()> _recipientsReady;
std::vector<not_null<PeerData*>> _recipients;
rpl::event_stream<ConnectedBot> _connected; rpl::event_stream<ConnectedBot> _connected;
rpl::event_stream<> _addForBot; rpl::event_stream<> _addForBot;
@ -104,6 +107,7 @@ private:
TimeId _offsetDate = 0; TimeId _offsetDate = 0;
QString _offsetThing; QString _offsetThing;
bool _allLoaded = false; bool _allLoaded = false;
bool _recipientsRequested = false;
rpl::variable<int> _rowCount = 0; rpl::variable<int> _rowCount = 0;
@ -328,17 +332,50 @@ void ListController::rowClicked(not_null<PeerListRow*> row) {
} }
void ListController::open(not_null<UserData*> bot, ConnectedBotState state) { void ListController::open(not_null<UserData*> bot, ConnectedBotState state) {
const auto show = _controller->uiShow();
if (_type == JoinType::Joined || !state.link.isEmpty()) { if (_type == JoinType::Joined || !state.link.isEmpty()) {
_controller->show(StarRefLinkBox({ bot, state }, _peer)); _recipientsReady = nullptr;
show->show(StarRefLinkBox({ bot, state }, _peer));
} else { } else {
const auto requireOthers = (_type == JoinType::Existing)
|| _peer->isSelf();
const auto requestOthers = requireOthers && _recipients.empty();
if (requestOthers) {
_recipientsReady = [=] {
Expects(!_recipients.empty());
open(bot, state);
};
requestRecipients();
return;
}
const auto connected = crl::guard(this, [=](ConnectedBotState now) { const auto connected = crl::guard(this, [=](ConnectedBotState now) {
_states[bot] = now; _states[bot] = now;
_connected.fire({ bot, now }); _connected.fire({ bot, now });
}); });
_controller->show(JoinStarRefBox({ bot, state }, _peer, connected)); show->show(JoinStarRefBox(
{ bot, state },
_peer,
requireOthers ? _recipients : std::vector<not_null<PeerData*>>(),
connected));
} }
} }
void ListController::requestRecipients() {
if (_recipientsRequested) {
return;
}
_recipientsRequested = true;
const auto session = &this->session();
ResolveRecipients(session, crl::guard(this, [=](
std::vector<not_null<PeerData*>> list) {
_recipients = std::move(list);
if (const auto callback = base::take(_recipientsReady)) {
callback();
}
}));
}
void RevokeLink( void RevokeLink(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<PeerData*> peer, not_null<PeerData*> peer,

View file

@ -2142,6 +2142,29 @@ void ActionsFiller::addAffiliateProgram(not_null<UserData*> user) {
}); });
const auto show = _controller->uiShow(); const auto show = _controller->uiShow();
struct StarRefRecipients {
std::vector<not_null<PeerData*>> list;
bool requested = false;
Fn<void()> open;
};
const auto recipients = std::make_shared<StarRefRecipients>();
recipients->open = [=] {
if (!recipients->list.empty()) {
const auto program = user->botInfo->starRefProgram;
show->show(Info::BotStarRef::JoinStarRefBox(
{ user, { program } },
user->session().user(),
recipients->list));
} else if (!recipients->requested) {
recipients->requested = true;
const auto done = [=](std::vector<not_null<PeerData*>> list) {
recipients->list = std::move(list);
recipients->open();
};
Info::BotStarRef::ResolveRecipients(&user->session(), done);
}
};
Ui::AddSkip(inner); Ui::AddSkip(inner);
::Settings::AddButtonWithLabel( ::Settings::AddButtonWithLabel(
inner, inner,
@ -2149,12 +2172,7 @@ void ActionsFiller::addAffiliateProgram(not_null<UserData*> user) {
rpl::duplicate(commission), rpl::duplicate(commission),
st::infoSharedMediaButton, st::infoSharedMediaButton,
{ &st::menuIconSharing } { &st::menuIconSharing }
)->setClickedCallback([=] { )->setClickedCallback(recipients->open);
const auto program = user->botInfo->starRefProgram;
show->show(Info::BotStarRef::JoinStarRefBox(
{ user, { program } },
user->session().user()));
});
Ui::AddSkip(inner); Ui::AddSkip(inner);
Ui::AddDividerText( Ui::AddDividerText(
inner, inner,

View file

@ -963,6 +963,11 @@ QString FormatStarsAmountDecimal(StarsAmount amount) {
return FormatExactCountDecimal(amount.value()); return FormatExactCountDecimal(amount.value());
} }
QString FormatStarsAmountRounded(StarsAmount amount) {
const auto value = amount.value();
return FormatExactCountDecimal(base::SafeRound(value * 100.) / 100.);
}
PluralResult Plural( PluralResult Plural(
ushort keyBase, ushort keyBase,
float64 value, float64 value,

View file

@ -31,6 +31,7 @@ struct ShortenedCount {
[[nodiscard]] QString FormatExactCountDecimal(float64 number); [[nodiscard]] QString FormatExactCountDecimal(float64 number);
[[nodiscard]] ShortenedCount FormatStarsAmountToShort(StarsAmount amount); [[nodiscard]] ShortenedCount FormatStarsAmountToShort(StarsAmount amount);
[[nodiscard]] QString FormatStarsAmountDecimal(StarsAmount amount); [[nodiscard]] QString FormatStarsAmountDecimal(StarsAmount amount);
[[nodiscard]] QString FormatStarsAmountRounded(StarsAmount amount);
struct PluralResult { struct PluralResult {
int keyShift = 0; int keyShift = 0;

View file

@ -1177,6 +1177,8 @@ botEmojiStatusUserpic: UserpicButton(defaultUserpicButton) {
photoSize: chatGiveawayPeerSize; photoSize: chatGiveawayPeerSize;
} }
botEmojiStatusName: FlatLabel(defaultFlatLabel) { botEmojiStatusName: FlatLabel(defaultFlatLabel) {
minWidth: 32px;
maxHeight: 20px;
} }
botEmojiStatusEmoji: FlatLabel(botEmojiStatusName) { botEmojiStatusEmoji: FlatLabel(botEmojiStatusName) {
textFg: profileVerifiedCheckBg; textFg: profileVerifiedCheckBg;

View file

@ -796,7 +796,8 @@ void WhoReactedEntryAction::paint(Painter &&p) {
if (selected && _st.itemBgOver->c.alpha() < 255) { if (selected && _st.itemBgOver->c.alpha() < 255) {
p.fillRect(0, 0, width(), _height, _st.itemBg); p.fillRect(0, 0, width(), _height, _st.itemBg);
} }
p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg); const auto bg = selected ? _st.itemBgOver : _st.itemBg;
p.fillRect(0, 0, width(), _height, bg);
if (enabled) { if (enabled) {
paintRipple(p, 0, 0); paintRipple(p, 0, 0);
} }
@ -817,6 +818,18 @@ void WhoReactedEntryAction::paint(Painter &&p) {
p.drawEllipse(photoLeft, photoTop, photoSize, photoSize); p.drawEllipse(photoLeft, photoTop, photoSize, photoSize);
} else if (!_userpic.isNull()) { } else if (!_userpic.isNull()) {
p.drawImage(photoLeft, photoTop, _userpic); p.drawImage(photoLeft, photoTop, _userpic);
if (_type == WhoReactedType::RefRecipientNow) {
auto hq = PainterHighQualityEnabler(p);
p.setBrush(Qt::NoBrush);
auto bgPen = bg->p;
bgPen.setWidthF(st::lineWidth * 6.);
p.setPen(bgPen);
p.drawEllipse(photoLeft, photoTop, photoSize, photoSize);
auto fgPen = st::windowBgActive->p;
fgPen.setWidthF(st::lineWidth * 2.);
p.setPen(fgPen);
p.drawEllipse(photoLeft, photoTop, photoSize, photoSize);
}
} else if (!_custom) { } else if (!_custom) {
st::menuIconReactions.paintInCenter( st::menuIconReactions.paintInCenter(
p, p,
@ -852,7 +865,16 @@ void WhoReactedEntryAction::paint(Painter &&p) {
_textWidth, _textWidth,
width()); width());
} }
if (withDate) { if (_type == WhoReactedType::RefRecipient
|| _type == WhoReactedType::RefRecipientNow) {
p.setPen(selected ? _st.itemFgShortcutOver : _st.itemFgShortcut);
_date.drawLeftElided(
p,
st::defaultWhoRead.nameLeft,
st::whoReadDateTop,
_textWidth,
width());
} else if (withDate) {
const auto iconPosition = QPoint( const auto iconPosition = QPoint(
st::defaultWhoRead.nameLeft, st::defaultWhoRead.nameLeft,
st::whoReadDateTop) + st::whoReadDateChecksPosition; st::whoReadDateTop) + st::whoReadDateChecksPosition;

View file

@ -74,6 +74,8 @@ enum class WhoReactedType : uchar {
Reposted, Reposted,
Forwarded, Forwarded,
Preloader, Preloader,
RefRecipient,
RefRecipientNow,
}; };
struct WhoReactedEntryData { struct WhoReactedEntryData {

View file

@ -475,6 +475,17 @@ starrefLinkCountIcon: icon{{ "chat/mini_subscribers", historyPeerUserpicFg }};
starrefLinkCountIconPosition: point(0px, 1px); starrefLinkCountIconPosition: point(0px, 1px);
starrefLinkCountFont: font(10px bold); starrefLinkCountFont: font(10px bold);
starrefLinkCountPadding: margins(2px, 0px, 3px, 1px); starrefLinkCountPadding: margins(2px, 0px, 3px, 1px);
starrefRecipientBg: lightButtonBgOver;
starrefRecipientBgDisabled: windowBgOver;
starrefRecipientArrow: icon{{ "calendar_down", lightButtonFg }};
starrefAddForBotIcon: icon {{ "menu/bot_add", lightButtonFg }}; starrefAddForBotIcon: icon {{ "menu/bot_add", lightButtonFg }};
starrefAddForBotIconPosition: point(23px, 2px); starrefAddForBotIconPosition: point(23px, 2px);
starrefPopupMenu: PopupMenu(defaultPopupMenu) {
maxHeight: 320px;
menu: Menu(defaultMenu) {
widthMin: 156px;
widthMax: 200px;
}
}

@ -1 +1 @@
Subproject commit 3254aebf55133066e0a8775cabdfcef568b79625 Subproject commit 93c1299a39258199d0336b33dc49cab029371785