Show and edit channel gifts notify settings.

This commit is contained in:
John Preston 2025-01-21 11:58:00 +04:00
parent c22d76e5be
commit fa4e74ffef
9 changed files with 217 additions and 70 deletions

View file

@ -2025,9 +2025,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_gift_sent" = "You sent a gift for {cost}";
"lng_action_gift_unique_sent" = "You sent a unique collectible item";
"lng_action_gift_upgraded" = "{user} turned the gift from you into a unique collectible";
"lng_action_gift_upgraded_channel" = "{user} turned this gift to {channel} into a unique collectible";
"lng_action_gift_upgraded_mine" = "You turned the gift from {user} into a unique collectible";
"lng_action_gift_upgraded_self" = "You turned this gift into a unique collectible";
"lng_action_gift_transferred" = "{user} transferred you a gift";
"lng_action_gift_transferred_channel" = "{user} transferred a gift to {channel}";
"lng_action_gift_transferred_mine" = "You transferred a gift to {user}";
"lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}";
"lng_action_gift_sent_channel" = "{user} sent a gift to {name} for {cost}";
@ -3323,6 +3325,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_convert_sure_title" = "Convert Gift to Stars";
"lng_gift_convert_sure_confirm#one" = "Do you want to convert this gift from {user} to **{count} Star**?";
"lng_gift_convert_sure_confirm#other" = "Do you want to convert this gift from {user} to **{count} Stars**?";
"lng_gift_convert_sure_confirm_channel#one" = "Do you want to convert this gift to {channel} to **{count} Star**?";
"lng_gift_convert_sure_confirm_channel#other" = "Do you want to convert this gift to {channel} to **{count} Stars**?";
"lng_gift_convert_sure_limit#one" = "Conversion is available for the next **{count} day**.";
"lng_gift_convert_sure_limit#other" = "Conversion is available for the next **{count} days**.";
"lng_gift_convert_sure_caution" = "This action cannot be undone. This will permanently destroy the gift.";

View file

@ -659,6 +659,13 @@ bool PeerData::canManageGifts() const {
return isSelf();
}
bool PeerData::canTransferGifts() const {
if (const auto channel = asChannel()) {
return channel->amCreator();
}
return isSelf();
}
bool PeerData::canEditMessagesIndefinitely() const {
if (const auto user = asUser()) {
return user->isSelf();

View file

@ -385,6 +385,7 @@ public:
[[nodiscard]] bool canCreateTopics() const;
[[nodiscard]] bool canManageTopics() const;
[[nodiscard]] bool canManageGifts() const;
[[nodiscard]] bool canTransferGifts() const;
[[nodiscard]] bool canExportChatHistory() const;
// Returns true if about text was changed.

View file

@ -5456,7 +5456,6 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
const auto from = fromId ? peer->owner().peer(fromId) : peer;
const auto channel = peer->owner().channel(
peerToChannel(giftPeer));
Assert(channel != nullptr);
result.links.push_back(from->createOpenLink());
result.links.push_back(channel->createOpenLink());
result.text = tr::lng_action_gift_sent_channel(
@ -5500,23 +5499,48 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
const MTPDmessageActionStarGiftUnique &action) {
auto result = PreparedServiceText();
const auto isSelf = _from->isSelf();
const auto giftPeer = action.vpeer()
? peerFromMTP(*action.vpeer())
: PeerId();
const auto service = _from->isServiceUser();
const auto toChannel = service && peerIsChannel(giftPeer);
const auto peer = isSelf ? _history->peer : _from;
result.links.push_back(peer->createOpenLink());
result.text = _history->peer->isSelf()
? tr::lng_action_gift_upgraded_self(
tr::now,
Ui::Text::WithEntities)
: (action.is_upgrade()
? (isSelf
? tr::lng_action_gift_upgraded_mine
: tr::lng_action_gift_upgraded)
: (isSelf
? tr::lng_action_gift_transferred_mine
: tr::lng_action_gift_transferred))(
tr::now,
lt_user,
Ui::Text::Link(peer->shortName(), 1), // Link 1.
Ui::Text::WithEntities);
if (toChannel) {
const auto fromId = action.vfrom_id()
? peerFromMTP(*action.vfrom_id())
: PeerId();
const auto channel = peer->owner().channel(
peerToChannel(giftPeer));
const auto from = fromId ? peer->owner().peer(fromId) : peer;
result.links.push_back(from->createOpenLink());
result.links.push_back(channel->createOpenLink());
result.text = (action.is_upgrade()
? tr::lng_action_gift_upgraded_channel
: tr::lng_action_gift_transferred_channel)(
tr::now,
lt_user,
Ui::Text::Link(from->shortName(), 1),
lt_channel,
Ui::Text::Link(channel->name(), 2),
Ui::Text::WithEntities);
} else {
result.links.push_back(peer->createOpenLink());
result.text = _history->peer->isSelf()
? tr::lng_action_gift_upgraded_self(
tr::now,
Ui::Text::WithEntities)
: (action.is_upgrade()
? (isSelf
? tr::lng_action_gift_upgraded_mine
: tr::lng_action_gift_upgraded)
: (isSelf
? tr::lng_action_gift_transferred_mine
: tr::lng_action_gift_transferred))(
tr::now,
lt_user,
Ui::Text::Link(peer->shortName(), 1), // Link 1.
Ui::Text::WithEntities);
}
return result;
};

View file

@ -366,7 +366,7 @@ rpl::producer<int> ContentWidget::scrollBottomSkipValue() const {
return _scrollBottomSkip.value();
}
rpl::producer<bool> ContentWidget::desiredBottomShadowVisibility() const {
rpl::producer<bool> ContentWidget::desiredBottomShadowVisibility() {
using namespace rpl::mappers;
return rpl::combine(
_scroll->scrollTopValue(),

View file

@ -131,7 +131,8 @@ public:
[[nodiscard]] int scrollBottomSkip() const;
[[nodiscard]] rpl::producer<int> scrollBottomSkipValue() const;
[[nodiscard]] rpl::producer<bool> desiredBottomShadowVisibility() const;
[[nodiscard]] virtual auto desiredBottomShadowVisibility()
-> rpl::producer<bool>;
protected:
template <typename Widget>

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/peer_gifts/info_peer_gifts_widget.h"
#include "api/api_premium.h"
#include "apiwrap.h"
#include "data/data_channel.h"
#include "data/data_session.h"
#include "data/data_user.h"
@ -16,7 +17,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/box_content_divider.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/ui_utility.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
@ -24,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "settings/settings_credits_graphics.h"
#include "styles/style_info.h"
#include "styles/style_layers.h" // boxRadius
#include "styles/style_credits.h" // giftBoxPadding
namespace Info::PeerGifts {
@ -58,6 +62,9 @@ public:
[[nodiscard]] not_null<PeerData*> peer() const {
return _peer;
}
[[nodiscard]] rpl::producer<bool> notifyEnabled() const {
return _notifyEnabled.events();
}
void saveState(not_null<Memento*> memento);
void restoreState(not_null<Memento*> memento);
@ -98,6 +105,7 @@ private:
QString _offset;
bool _allLoaded = false;
rpl::event_stream<bool> _notifyEnabled;
std::vector<View> _views;
int _viewsForWidth = 0;
int _viewsFromRow = 0;
@ -119,22 +127,23 @@ InnerWidget::InnerWidget(
, _window(controller->parentController())
, _delegate(_window, GiftButtonMode::Minimal)
, _controller(controller)
, _about(std::make_unique<Ui::FlatLabel>(
this,
(peer->isSelf()
? tr::lng_peer_gifts_about_mine(Ui::Text::RichLangValue)
: tr::lng_peer_gifts_about(
lt_user,
rpl::single(Ui::Text::Bold(peer->shortName())),
Ui::Text::RichLangValue)),
st::giftListAbout))
, _about((!peer->isSelf() && peer->canManageGifts())
? nullptr
: std::make_unique<Ui::FlatLabel>(
this,
(peer->isSelf()
? tr::lng_peer_gifts_about_mine(Ui::Text::RichLangValue)
: tr::lng_peer_gifts_about(
lt_user,
rpl::single(Ui::Text::Bold(peer->shortName())),
Ui::Text::RichLangValue)),
st::giftListAbout))
, _peer(peer)
, _totalCount(_peer->peerGiftsCount())
, _api(&_peer->session().mtp()) {
_singleMin = _delegate.buttonSize();
const auto channel = peer->asBroadcast();
if (peer->isSelf() || (channel && channel->canManageGifts())) {
if (peer->canManageGifts()) {
subscribeToUpdates();
}
}
@ -196,11 +205,15 @@ void InnerWidget::visibleTopBottomUpdated(
void InnerWidget::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
const auto aboutSize = _about->size().grownBy(st::giftListAboutMargin);
const auto aboutSize = _about
? _about->size().grownBy(st::giftListAboutMargin)
: QSize();
const auto skips = QMargins(0, 0, 0, aboutSize.height());
p.fillRect(rect().marginsRemoved(skips), st::boxDividerBg->c);
paintTop(p);
paintBottom(p, skips.bottom());
if (const auto bottom = skips.bottom()) {
paintBottom(p, bottom);
}
}
void InnerWidget::loadMore() {
@ -208,8 +221,7 @@ void InnerWidget::loadMore() {
return;
}
using Flag = MTPpayments_GetSavedStarGifts::Flag;
const auto withUnsaved = _peer->isSelf()
|| (_peer->isChannel() && _peer->asChannel()->canManageGifts());
const auto withUnsaved = _peer->canManageGifts();
_loadMoreRequestId = _api.request(MTPpayments_GetSavedStarGifts(
MTP_flags(withUnsaved ? Flag() : Flag::f_exclude_unsaved),
_peer->input,
@ -218,6 +230,9 @@ void InnerWidget::loadMore() {
)).done([=](const MTPpayments_SavedStarGifts &result) {
_loadMoreRequestId = 0;
const auto &data = result.data();
if (const auto enabled = data.vchat_notifications_enabled()) {
_notifyEnabled.fire(mtpIsTrue(*enabled));
}
if (const auto next = data.vnext_offset()) {
_offset = qs(*next);
} else {
@ -361,10 +376,12 @@ int InnerWidget::resizeGetHeight(int width) {
auto result = padding.bottom() * 2 + rows * (singleh + skiph) - skiph;
const auto margin = st::giftListAboutMargin;
_about->resizeToWidth(width - margin.left() - margin.right());
_about->moveToLeft(margin.left(), result + margin.top());
result += margin.top() + _about->height() + margin.bottom();
if (const auto about = _about.get()) {
const auto margin = st::giftListAboutMargin;
about->resizeToWidth(width - margin.left() - margin.right());
about->moveToLeft(margin.left(), result + margin.top());
result += margin.top() + about->height() + margin.bottom();
}
return result;
}
@ -412,16 +429,91 @@ Widget::Widget(
not_null<Controller*> controller,
not_null<PeerData*> peer)
: ContentWidget(parent, controller) {
_inner = setInnerWidget(object_ptr<InnerWidget>(
_inner = setInnerWidget(
object_ptr<InnerWidget>(this, controller, peer));
_inner->notifyEnabled(
) | rpl::take(1) | rpl::start_with_next([=](bool enabled) {
setupNotifyCheckbox(enabled);
}, _inner->lifetime());
}
void Widget::showFinished() {
_shown = true;
if (const auto bottom = _pinnedToBottom.data()) {
bottom->toggle(true, anim::type::normal);
}
}
void Widget::setupNotifyCheckbox(bool enabled) {
_pinnedToBottom = Ui::CreateChild<Ui::SlideWrap<Ui::RpWidget>>(
this,
controller,
peer));
object_ptr<Ui::RpWidget>(this));
const auto wrap = _pinnedToBottom.data();
wrap->toggle(false, anim::type::instant);
const auto bottom = wrap->entity();
bottom->show();
const auto notify = Ui::CreateChild<Ui::Checkbox>(
bottom,
tr::lng_peer_gifts_notify(),
enabled);
notify->show();
notify->checkedChanges() | rpl::start_with_next([=](bool checked) {
const auto api = &controller()->session().api();
using Flag = MTPpayments_ToggleChatStarGiftNotifications::Flag;
api->request(MTPpayments_ToggleChatStarGiftNotifications(
MTP_flags(checked ? Flag::f_enabled : Flag()),
_inner->peer()->input
)).send();
}, notify->lifetime());
const auto &checkSt = st::defaultCheckbox;
const auto checkTop = st::boxRadius + checkSt.margin.top();
bottom->widthValue() | rpl::start_with_next([=](int width) {
const auto normal = notify->naturalWidth()
- checkSt.margin.left()
- checkSt.margin.right();
notify->resizeToWidth(normal);
const auto checkLeft = (width - normal) / 2;
notify->moveToLeft(checkLeft, checkTop);
}, notify->lifetime());
notify->heightValue() | rpl::start_with_next([=](int height) {
bottom->resize(bottom->width(), st::boxRadius + height);
}, notify->lifetime());
const auto processHeight = [=] {
setScrollBottomSkip(wrap->height());
wrap->moveToLeft(wrap->x(), height() - wrap->height());
};
_inner->sizeValue(
) | rpl::start_with_next([=](const QSize &s) {
wrap->resizeToWidth(s.width());
crl::on_main(wrap, processHeight);
}, wrap->lifetime());
rpl::combine(
wrap->heightValue(),
heightValue()
) | rpl::start_with_next(processHeight, wrap->lifetime());
if (_shown) {
wrap->toggle(true, anim::type::normal);
}
_hasPinnedToBottom = true;
}
rpl::producer<QString> Widget::title() {
return tr::lng_peer_gifts_title();
}
rpl::producer<bool> Widget::desiredBottomShadowVisibility() {
return _hasPinnedToBottom.value();
}
not_null<PeerData*> Widget::peer() const {
return _inner->peer();
}

View file

@ -13,6 +13,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class UserData;
struct PeerListState;
namespace Ui {
class RpWidget;
template <typename Widget>
class SlideWrap;
} // namespace Ui
namespace Info::PeerGifts {
struct ListState {
@ -61,13 +67,22 @@ public:
rpl::producer<QString> title() override;
rpl::producer<bool> desiredBottomShadowVisibility() override;
void showFinished() override;
private:
void saveState(not_null<Memento*> memento);
void restoreState(not_null<Memento*> memento);
std::shared_ptr<ContentMemento> doCreateMemento() override;
void setupNotifyCheckbox(bool enabled);
InnerWidget *_inner = nullptr;
QPointer<Ui::SlideWrap<Ui::RpWidget>> _pinnedToBottom;
rpl::variable<bool> _hasPinnedToBottom;
bool _shown = false;
};

View file

@ -184,7 +184,6 @@ private:
void ToggleStarGiftSaved(
std::shared_ptr<ChatHelpers::Show> show,
not_null<UserData*> sender,
Data::SavedStarGiftId savedId,
bool save,
Fn<void(bool)> done) {
@ -206,17 +205,12 @@ void ToggleStarGiftSaved(
void ConfirmConvertStarGift(
std::shared_ptr<Ui::Show> show,
QString name,
rpl::producer<TextWithEntities> confirmText,
int stars,
int daysLeft,
Fn<void()> convert) {
auto text = rpl::combine(
tr::lng_gift_convert_sure_confirm(
lt_count,
rpl::single(stars * 1.),
lt_user,
rpl::single(Ui::Text::Bold(name)),
Ui::Text::RichLangValue),
std::move(confirmText),
tr::lng_gift_convert_sure_limit(
lt_count,
rpl::single(daysLeft * 1.),
@ -238,7 +232,6 @@ void ConfirmConvertStarGift(
void ConvertStarGift(
std::shared_ptr<ChatHelpers::Show> show,
not_null<UserData*> sender,
Data::SavedStarGiftId savedId,
int stars,
Fn<void(bool)> done) {
@ -931,6 +924,7 @@ void GenericCreditsEntryBox(
const Data::SubscriptionEntry &s,
CreditsEntryBoxStyleOverrides st) {
const auto session = &show->session();
const auto selfPeerId = session->userPeerId().value;
const auto owner = &session->data();
const auto item = owner->message(
PeerId(e.barePeerId),
@ -939,7 +933,19 @@ void GenericCreditsEntryBox(
const auto creditsHistoryStarGift = isStarGift && !e.id.isEmpty();
const auto sentStarGift = creditsHistoryStarGift && !e.in;
const auto convertedStarGift = creditsHistoryStarGift && e.converted;
const auto gotStarGift = isStarGift && !creditsHistoryStarGift && e.in;
const auto giftToSelf = isStarGift
&& (e.barePeerId == selfPeerId)
&& (e.in || e.bareGiftOwnerId == selfPeerId);
const auto giftChannel = (isStarGift && e.giftSavedId)
? session->data().peer(
PeerId(e.bareGiftListPeerId))->asChannel()
: nullptr;
const auto giftToChannel = (giftChannel != nullptr);
const auto giftToChannelCanManage = giftToChannel
&& giftChannel->canManageGifts();
const auto gotStarGift = isStarGift
&& !creditsHistoryStarGift
&& (e.in || giftToChannelCanManage);
const auto starGiftSender = (isStarGift && item)
? item->history()->peer->asUser()
: (isStarGift && e.in)
@ -1109,16 +1115,6 @@ void GenericCreditsEntryBox(
}, widget->lifetime());
}
const auto selfPeerId = session->userPeerId().value;
const auto giftToSelf = isStarGift
&& (e.barePeerId == selfPeerId)
&& (e.in || e.bareGiftOwnerId == selfPeerId);
const auto giftToChannel = isStarGift && e.giftSavedId;
const auto giftToChannelCanManage = isStarGift
&& e.giftSavedId
&& session->data().peer(
PeerId(e.bareGiftListPeerId))->canManageGifts();
if (!uniqueGift) {
Ui::AddSkip(content);
Ui::AddSkip(content);
@ -1428,7 +1424,7 @@ void GenericCreditsEntryBox(
}
}
};
ToggleStarGiftSaved(show, starGiftSender, savedId, save, done);
ToggleStarGiftSaved(show, savedId, save, done);
};
const auto upgradeGuard = std::make_shared<bool>();
@ -1469,8 +1465,20 @@ void GenericCreditsEntryBox(
const auto convert = [=, weak = Ui::MakeWeak(box)] {
const auto stars = e.starsConverted;
const auto days = canConvert ? ((timeLeft + 86399) / 86400) : 0;
const auto name = starGiftSender->shortName();
ConfirmConvertStarGift(show, name, stars, days, [=] {
auto text = giftToChannelCanManage
? tr::lng_gift_convert_sure_confirm_channel(
lt_count,
rpl::single(stars * 1.),
lt_channel,
rpl::single(Ui::Text::Bold(giftChannel->name())),
Ui::Text::RichLangValue)
: tr::lng_gift_convert_sure_confirm(
lt_count,
rpl::single(stars * 1.),
lt_user,
rpl::single(Ui::Text::Bold(starGiftSender->shortName())),
Ui::Text::RichLangValue);
ConfirmConvertStarGift(show, std::move(text), stars, days, [=] {
if (state->convertButtonBusy.current()
|| state->confirmButtonBusy.current()) {
return;
@ -1496,12 +1504,7 @@ void GenericCreditsEntryBox(
}
}
};
ConvertStarGift(
show,
starGiftSender,
savedId,
stars,
done);
ConvertStarGift(show, savedId, stars, done);
}
});
};