Add pays-me status bar in chat.

This commit is contained in:
John Preston 2025-02-28 17:26:02 +04:00
parent 8ea7bd4913
commit 9032489786
8 changed files with 284 additions and 20 deletions

View file

@ -918,6 +918,10 @@ historyBusinessBotSettings: IconButton(defaultIconButton) {
height: 58px;
width: 48px;
}
paysStatusLabel: FlatLabel(historyBusinessBotStatus) {
align: align(top);
minWidth: 240px;
}
historyReplyCancelIcon: icon {{ "box_button_close", historyReplyCancelFg }};
historyReplyCancelIconOver: icon {{ "box_button_close", historyReplyCancelFgOver }};

View file

@ -95,27 +95,28 @@ struct PeerUpdate {
Birthday = (1ULL << 33),
PersonalChannel = (1ULL << 34),
StarRefProgram = (1ULL << 35),
PaysPerMessage = (1ULL << 36),
// For chats and channels
InviteLinks = (1ULL << 36),
Members = (1ULL << 37),
Admins = (1ULL << 38),
BannedUsers = (1ULL << 39),
Rights = (1ULL << 40),
PendingRequests = (1ULL << 41),
Reactions = (1ULL << 42),
InviteLinks = (1ULL << 37),
Members = (1ULL << 38),
Admins = (1ULL << 39),
BannedUsers = (1ULL << 40),
Rights = (1ULL << 41),
PendingRequests = (1ULL << 42),
Reactions = (1ULL << 43),
// For channels
ChannelAmIn = (1ULL << 43),
StickersSet = (1ULL << 44),
EmojiSet = (1ULL << 45),
ChannelLinkedChat = (1ULL << 46),
ChannelLocation = (1ULL << 47),
Slowmode = (1ULL << 48),
GroupCall = (1ULL << 49),
ChannelAmIn = (1ULL << 44),
StickersSet = (1ULL << 45),
EmojiSet = (1ULL << 46),
ChannelLinkedChat = (1ULL << 47),
ChannelLocation = (1ULL << 48),
Slowmode = (1ULL << 49),
GroupCall = (1ULL << 50),
// For iteration
LastUsedBit = (1ULL << 49),
LastUsedBit = (1ULL << 50),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }

View file

@ -734,7 +734,7 @@ void PeerData::checkFolder(FolderId folderId) {
void PeerData::clearBusinessBot() {
if (const auto details = _barDetails.get()) {
if (details->requestChatDate) {
if (details->requestChatDate || details->paysPerMessage) {
details->businessBot = nullptr;
details->businessBotManageUrl = QString();
} else {
@ -777,7 +777,10 @@ void PeerData::saveTranslationDisabled(bool disabled) {
void PeerData::setBarSettings(const MTPPeerSettings &data) {
data.match([&](const MTPDpeerSettings &data) {
if (!data.vbusiness_bot_id() && !data.vrequest_chat_title()) {
const auto wasPaysPerMessage = paysPerMessage();
if (!data.vbusiness_bot_id()
&& !data.vrequest_chat_title()
&& !data.vcharge_paid_message_stars()) {
_barDetails = nullptr;
} else if (!_barDetails) {
_barDetails = std::make_unique<PeerBarDetails>();
@ -792,6 +795,8 @@ void PeerData::setBarSettings(const MTPPeerSettings &data) {
: nullptr;
_barDetails->businessBotManageUrl
= qs(data.vbusiness_bot_manage_url().value_or_empty());
_barDetails->paysPerMessage
= data.vcharge_paid_message_stars().value_or_empty();
}
using Flag = PeerBarSetting;
setBarSettings((data.is_add_contact() ? Flag::AddContact : Flag())
@ -815,8 +820,33 @@ void PeerData::setBarSettings(const MTPPeerSettings &data) {
| (data.is_business_bot_can_reply()
? Flag::BusinessBotCanReply
: Flag()));
if (wasPaysPerMessage != paysPerMessage()) {
session().changes().peerUpdated(
this,
UpdateFlag::PaysPerMessage);
}
});
}
int PeerData::paysPerMessage() const {
return _barDetails ? _barDetails->paysPerMessage : 0;
}
void PeerData::clearPaysPerMessage() {
if (const auto details = _barDetails.get()) {
if (details->paysPerMessage) {
if (details->businessBot || details->requestChatDate) {
details->paysPerMessage = 0;
} else {
_barDetails = nullptr;
}
session().changes().peerUpdated(
this,
UpdateFlag::PaysPerMessage);
}
}
}
QString PeerData::requestChatTitle() const {
return _barDetails ? _barDetails->requestChatTitle : QString();
}

View file

@ -177,6 +177,7 @@ struct PeerBarDetails {
TimeId requestChatDate;
UserData *businessBot = nullptr;
QString businessBotManageUrl;
int paysPerMessage = 0;
};
class PeerData {
@ -412,6 +413,8 @@ public:
? _barSettings.changes()
: (_barSettings.value() | rpl::type_erased());
}
[[nodiscard]] int paysPerMessage() const;
void clearPaysPerMessage();
[[nodiscard]] QString requestChatTitle() const;
[[nodiscard]] TimeId requestChatDate() const;
[[nodiscard]] UserData *businessBot() const;

View file

@ -1693,6 +1693,9 @@ void HistoryWidget::orderWidgets() {
if (_contactStatus) {
_contactStatus->bar().raise();
}
if (_paysStatus) {
_paysStatus->bar().raise();
}
if (_translateBar) {
_translateBar->raise();
}
@ -2416,6 +2419,7 @@ void HistoryWidget::showHistory(
_showAtMsgId = showAtMsgId;
_showAtMsgParams = params;
_historyInited = false;
_paysStatus = nullptr;
_contactStatus = nullptr;
_businessBotStatus = nullptr;
@ -2436,6 +2440,14 @@ void HistoryWidget::showHistory(
refreshGiftToChannelShown();
if (const auto user = _peer->asUser()) {
_paysStatus = std::make_unique<PaysStatus>(
controller(),
this,
user);
_paysStatus->bar().heightValue(
) | rpl::start_with_next([=] {
updateControlsGeometry();
}, _paysStatus->bar().lifetime());
_businessBotStatus = std::make_unique<BusinessBotStatus>(
controller(),
this,
@ -3077,6 +3089,9 @@ void HistoryWidget::updateControlsVisibility() {
if (_requestsBar) {
_requestsBar->show();
}
if (_paysStatus) {
_paysStatus->show();
}
if (_contactStatus) {
_contactStatus->show();
}
@ -4305,6 +4320,9 @@ void HistoryWidget::hideChildWidgets() {
if (_chooseTheme) {
_chooseTheme->hide();
}
if (_paysStatus) {
_paysStatus->hide();
}
if (_contactStatus) {
_contactStatus->hide();
}
@ -6266,8 +6284,13 @@ void HistoryWidget::updateControlsGeometry() {
_translateBar->move(0, translateTop);
_translateBar->resizeToWidth(width());
}
const auto contactStatusTop = translateTop
const auto paysStatusTop = translateTop
+ (_translateBar ? _translateBar->height() : 0);
if (_paysStatus) {
_paysStatus->bar().move(0, paysStatusTop);
}
const auto contactStatusTop = paysStatusTop
+ (_paysStatus ? _paysStatus->bar().height() : 0);
if (_contactStatus) {
_contactStatus->bar().move(0, contactStatusTop);
}
@ -6518,6 +6541,9 @@ void HistoryWidget::updateHistoryGeometry(
if (_requestsBar) {
newScrollHeight -= _requestsBar->height();
}
if (_paysStatus) {
newScrollHeight -= _paysStatus->bar().height();
}
if (_contactStatus) {
newScrollHeight -= _contactStatus->bar().height();
}
@ -6940,6 +6966,7 @@ void HistoryWidget::botCallbackSent(not_null<HistoryItem*> item) {
int HistoryWidget::computeMaxFieldHeight() const {
const auto available = height()
- _topBar->height()
- (_paysStatus ? _paysStatus->bar().height() : 0)
- (_contactStatus ? _contactStatus->bar().height() : 0)
- (_businessBotStatus ? _businessBotStatus->bar().height() : 0)
- (_sponsoredMessageBar ? _sponsoredMessageBar->height() : 0)

View file

@ -100,6 +100,7 @@ namespace HistoryView {
class StickerToast;
class PaidReactionToast;
class TopBarWidget;
class PaysStatus;
class ContactStatus;
class BusinessBotStatus;
class Element;
@ -782,6 +783,7 @@ private:
Webrtc::RecordAvailability _recordAvailability = {};
std::unique_ptr<HistoryView::PaysStatus> _paysStatus;
std::unique_ptr<HistoryView::ContactStatus> _contactStatus;
std::unique_ptr<HistoryView::BusinessBotStatus> _businessBotStatus;

View file

@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_utilities.h"
#include "ui/boxes/confirm_box.h"
#include "ui/layers/generic_box.h"
#include "chat_helpers/message_field.h" // PaidSendButtonText
#include "core/click_handler_types.h"
#include "core/ui_integration.h"
#include "data/business/data_business_chatbots.h"
@ -595,9 +596,9 @@ auto ContactStatus::PeerState(not_null<PeerData*> peer)
return {
.type = Type::RequestChatInfo,
.requestChatName = peer->requestChatTitle(),
.requestDate = peer->requestChatDate(),
.requestChatIsBroadcast = !!(settings.value
& PeerBarSetting::RequestChatIsBroadcast),
.requestDate = peer->requestChatDate(),
};
} else if (settings.value & PeerBarSetting::AutoArchived) {
return { Type::UnarchiveOrBlock };
@ -1131,4 +1132,165 @@ void TopicReopenBar::setupHandler() {
});
}
class PaysStatus::Bar final : public Ui::RpWidget {
public:
Bar(QWidget *parent, not_null<PeerData*> peer);
void showState(State state);
[[nodiscard]] rpl::producer<> removeClicks() const;
private:
void paintEvent(QPaintEvent *e) override;
int resizeGetHeight(int newWidth) override;
not_null<PeerData*> _peer;
object_ptr<Ui::FlatLabel> _label;
object_ptr<Ui::LinkButton> _remove;
rpl::event_stream<> _removeClicks;
};
PaysStatus::Bar::Bar(QWidget *parent, not_null<PeerData*> peer)
: RpWidget(parent)
, _peer(peer)
, _label(this, st::paysStatusLabel)
, _remove(this, tr::lng_payment_bar_button(tr::now)) {
_label->setAttribute(Qt::WA_TransparentForMouseEvents);
}
void PaysStatus::Bar::showState(State state) {
_label->setMarkedText(tr::lng_payment_bar_text(
tr::now,
lt_name,
TextWithEntities{ _peer->shortName() },
lt_cost,
PaidSendButtonText(tr::now, state.perMessage),
Ui::Text::WithEntities));
resizeToWidth(width());
}
rpl::producer<> PaysStatus::Bar::removeClicks() const {
return _remove->clicks() | rpl::to_empty;
}
void PaysStatus::Bar::paintEvent(QPaintEvent *e) {
QPainter p(this);
p.fillRect(e->rect(), st::historyContactStatusButton.bgColor);
}
int PaysStatus::Bar::resizeGetHeight(int newWidth) {
const auto skip = st::defaultPeerListItem.photoPosition.y();
_label->resizeToWidth(newWidth - skip);
_label->moveToLeft(skip, skip, newWidth);
_remove->move(
(newWidth - _remove->width()) / 2,
skip + _label->height() + skip);
return _remove->y() + _remove->height() + skip;
}
PaysStatus::PaysStatus(
not_null<Window::SessionController*> window,
not_null<Ui::RpWidget*> parent,
not_null<UserData*> user)
: _controller(window)
, _user(user)
, _inner(Ui::CreateChild<Bar>(parent.get(), user))
, _bar(parent, object_ptr<Bar>::fromRaw(_inner)) {
setupState();
setupHandlers();
}
void PaysStatus::setupState() {
_user->session().api().requestPeerSettings(_user);
_user->session().changes().peerFlagsValue(
_user,
Data::PeerUpdate::Flag::PaysPerMessage
) | rpl::start_with_next([=] {
_state = State{ _user->paysPerMessage() };
if (_state.perMessage > 0) {
_inner->showState(_state);
_bar.toggleContent(true);
} else {
_bar.toggleContent(false);
}
}, _bar.lifetime());
}
void PaysStatus::setupHandlers() {
_inner->removeClicks(
) | rpl::start_with_next([=] {
const auto user = _user;
const auto exception = [=](bool refund) {
using Flag = MTPaccount_AddNoPaidMessagesException::Flag;
const auto api = &user->session().api();
api->request(MTPaccount_AddNoPaidMessagesException(
MTP_flags(refund ? Flag::f_refund_charged : Flag()),
user->inputUser
)).done([=] {
user->clearPaysPerMessage();
}).send();
};
_controller->show(Box([=](not_null<Ui::GenericBox*> box) {
const auto refund = std::make_shared<QPointer<Ui::Checkbox>>();
Ui::ConfirmBox(box, {
.text = tr::lng_payment_refund_text(
tr::now,
lt_name,
Ui::Text::Bold(user->shortName()),
Ui::Text::WithEntities),
.confirmed = [=](Fn<void()> close) {
exception(*refund && (*refund)->checked());
close();
},
.confirmText = tr::lng_payment_refund_confirm(tr::now),
.title = tr::lng_payment_refund_title(tr::now),
});
const auto paid = box->lifetime().make_state<
rpl::variable<int>
>();
*paid = _paidAlready.value();
paid->value() | rpl::start_with_next([=](int already) {
if (!already) {
delete base::take(*refund);
} else if (!*refund) {
const auto skip = st::defaultCheckbox.margin.top();
*refund = box->addRow(
object_ptr<Ui::Checkbox>(
box,
tr::lng_payment_refund_also(
lt_count,
paid->value() | tr::to_count()),
false,
st::defaultCheckbox),
st::boxRowPadding + QMargins(0, skip, 0, skip));
}
}, box->lifetime());
user->session().api().request(MTPaccount_GetPaidMessagesRevenue(
user->inputUser
)).done(crl::guard(_inner, [=](
const MTPaccount_PaidMessagesRevenue &result) {
_paidAlready = result.data().vstars_amount().v;
})).send();
}));
}, _bar.lifetime());
}
void PaysStatus::show() {
if (!_shown) {
_shown = true;
if (_state.perMessage > 0) {
_inner->showState(_state);
_bar.toggleContent(true);
}
}
_bar.show();
}
void PaysStatus::hide() {
_bar.hide();
}
} // namespace HistoryView

View file

@ -95,9 +95,10 @@ private:
RequestChatInfo,
};
Type type = Type::None;
int starsPerMessage = 0;
QString requestChatName;
bool requestChatIsBroadcast = false;
TimeId requestDate = 0;
bool requestChatIsBroadcast = false;
};
void setupState(not_null<PeerData*> peer, bool showInForum);
@ -181,4 +182,38 @@ private:
};
class PaysStatus final {
public:
PaysStatus(
not_null<Window::SessionController*> controller,
not_null<Ui::RpWidget*> parent,
not_null<UserData*> user);
void show();
void hide();
[[nodiscard]] SlidingBar &bar() {
return _bar;
}
private:
class Bar;
struct State {
int perMessage = 0;
};
void setupState();
void setupHandlers();
const not_null<Window::SessionController*> _controller;
const not_null<UserData*> _user;
rpl::variable<int> _paidAlready;
State _state;
QPointer<Bar> _inner;
SlidingBar _bar;
bool _shown = false;
};
} // namespace HistoryView