Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f05191e668 | ||
|
|
1ba189e59d | ||
|
|
c40ca70aa6 | ||
|
|
521f991167 | ||
|
|
edf1417bbb | ||
|
|
686e9643ad | ||
|
|
975460d268 | ||
|
|
6b0c606d25 | ||
|
|
93ff0bdcff | ||
|
|
bf9d90ca4e | ||
|
|
d6e5e1e8f7 | ||
|
|
d3ae2ef9ea | ||
|
|
40b0854704 | ||
|
|
9aec6b6496 | ||
|
|
d01f977960 | ||
|
|
1444009ee2 | ||
|
|
14ee9bee26 | ||
|
|
93587ddc3c | ||
|
|
0c4d03477e | ||
|
|
a35092f012 | ||
|
|
49ee7ee52b | ||
|
|
52c77a1970 | ||
|
|
e4269ae7fb | ||
|
|
bd6011c524 | ||
|
|
46cb3ec103 | ||
|
|
0241129948 | ||
|
|
e44aca06cb | ||
|
|
19d386f977 | ||
|
|
a67fdda913 | ||
|
|
5286c7b1c3 | ||
|
|
d9d96d0a6f | ||
|
|
13353bb615 | ||
|
|
d78348fd16 | ||
|
|
1e8e660133 | ||
|
|
5196982c98 | ||
|
|
99f857fbf6 | ||
|
|
0982aa166a | ||
|
|
3ae9f86097 | ||
|
|
9efd9b0d68 | ||
|
|
267a73f355 | ||
|
|
437d8ea890 | ||
|
|
7ed92ec402 | ||
|
|
678d9ffbf9 | ||
|
|
bc4b427ed1 | ||
|
|
36141a9df9 | ||
|
|
d68ba75457 | ||
|
|
00ad32c5f4 | ||
|
|
064bab60ff | ||
|
|
5b146217c0 | ||
|
|
202c81b2e5 | ||
|
|
4520480604 | ||
|
|
aea87bb5cb | ||
|
|
19492f7e7b | ||
|
|
51030e3c45 | ||
|
|
e6b8b4be18 | ||
|
|
6ac13b7f80 | ||
|
|
7e5e6003a9 | ||
|
|
f445440995 | ||
|
|
d81547f091 | ||
|
|
2b185d491b | ||
|
|
ca47440950 | ||
|
|
5e32602f4a | ||
|
|
1f4516028c | ||
|
|
7f29d269a3 | ||
|
|
62c3374911 | ||
|
|
2116e04af5 | ||
|
|
a97d5e80c7 | ||
|
|
b5098038d0 | ||
|
|
6fce718252 | ||
|
|
40cf96202d | ||
|
|
c18e59218e | ||
|
|
216865a20d | ||
|
|
6c5036ee8d | ||
|
|
dc2f59ca24 | ||
|
|
6f6457137e | ||
|
|
bc5cb6e2a2 | ||
|
|
16825fff41 |
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.4.6.0" />
|
||||
Version="5.5.2.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,4,6,0
|
||||
PRODUCTVERSION 5,4,6,0
|
||||
FILEVERSION 5,5,2,0
|
||||
PRODUCTVERSION 5,5,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "5.4.6.0"
|
||||
VALUE "FileVersion", "5.5.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.4.6.0"
|
||||
VALUE "ProductVersion", "5.5.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,4,6,0
|
||||
PRODUCTVERSION 5,4,6,0
|
||||
FILEVERSION 5,5,2,0
|
||||
PRODUCTVERSION 5,5,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "5.4.6.0"
|
||||
VALUE "FileVersion", "5.5.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.4.6.0"
|
||||
VALUE "ProductVersion", "5.5.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -232,12 +232,12 @@ void ImportInvite(
|
||||
api->request(MTPchatlists_JoinChatlistInvite(
|
||||
MTP_string(slug),
|
||||
MTP_vector<MTPInputPeer>(std::move(inputs))
|
||||
)).done(callback).fail(error).send();
|
||||
)).done(callback).fail(error).handleFloodErrors().send();
|
||||
} else {
|
||||
api->request(MTPchatlists_JoinChatlistUpdates(
|
||||
MTP_inputChatlistDialogFilter(MTP_int(filterId)),
|
||||
MTP_vector<MTPInputPeer>(std::move(inputs))
|
||||
)).done(callback).fail(error).send();
|
||||
)).done(callback).fail(error).handleFloodErrors().send();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,6 +517,8 @@ void ShowImportError(
|
||||
} else {
|
||||
window->showToast((error == u"INVITE_SLUG_EXPIRED"_q)
|
||||
? tr::lng_group_invite_bad_link(tr::now)
|
||||
: error.startsWith(u"FLOOD_WAIT_"_q)
|
||||
? tr::lng_flood_error(tr::now)
|
||||
: error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,14 +264,24 @@ ChatParticipant::ChatParticipant(
|
||||
_rank = qs(data.vrank().value_or_empty());
|
||||
_rights = ChatAdminRightsInfo(data.vadmin_rights());
|
||||
_by = peerToUser(peerFromUser(data.vpromoted_by()));
|
||||
_date = data.vdate().v;
|
||||
}, [&](const MTPDchannelParticipantSelf &data) {
|
||||
_type = Type::Member;
|
||||
_date = data.vdate().v;
|
||||
_by = peerToUser(peerFromUser(data.vinviter_id()));
|
||||
if (data.vsubscription_until_date()) {
|
||||
_subscriptionDate = data.vsubscription_until_date()->v;
|
||||
}
|
||||
}, [&](const MTPDchannelParticipant &data) {
|
||||
_type = Type::Member;
|
||||
_date = data.vdate().v;
|
||||
if (data.vsubscription_until_date()) {
|
||||
_subscriptionDate = data.vsubscription_until_date()->v;
|
||||
}
|
||||
}, [&](const MTPDchannelParticipantBanned &data) {
|
||||
_restrictions = ChatRestrictionsInfo(data.vbanned_rights());
|
||||
_by = peerToUser(peerFromUser(data.vkicked_by()));
|
||||
_date = data.vdate().v;
|
||||
|
||||
_type = (_restrictions.flags & ChatRestriction::ViewMessages)
|
||||
? Type::Banned
|
||||
@@ -348,6 +358,24 @@ ChatAdminRightsInfo ChatParticipant::rights() const {
|
||||
return _rights;
|
||||
}
|
||||
|
||||
TimeId ChatParticipant::subscriptionDate() const {
|
||||
return _subscriptionDate;
|
||||
}
|
||||
|
||||
TimeId ChatParticipant::promotedSince() const {
|
||||
return (_type == Type::Admin) ? _date : TimeId(0);
|
||||
}
|
||||
|
||||
TimeId ChatParticipant::restrictedSince() const {
|
||||
return (_type == Type::Restricted || _type == Type::Banned)
|
||||
? _date
|
||||
: TimeId(0);
|
||||
}
|
||||
|
||||
TimeId ChatParticipant::memberSince() const {
|
||||
return (_type == Type::Member) ? _date : TimeId(0);
|
||||
}
|
||||
|
||||
ChatParticipant::Type ChatParticipant::type() const {
|
||||
return _type;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,11 @@ public:
|
||||
ChatRestrictionsInfo restrictions() const;
|
||||
ChatAdminRightsInfo rights() const;
|
||||
|
||||
TimeId subscriptionDate() const;
|
||||
TimeId promotedSince() const;
|
||||
TimeId restrictedSince() const;
|
||||
TimeId memberSince() const;
|
||||
|
||||
Type type() const;
|
||||
QString rank() const;
|
||||
|
||||
@@ -73,6 +78,8 @@ private:
|
||||
bool _canBeEdited = false;
|
||||
|
||||
QString _rank;
|
||||
TimeId _subscriptionDate = 0;
|
||||
TimeId _date = 0;
|
||||
|
||||
ChatRestrictionsInfo _restrictions;
|
||||
ChatAdminRightsInfo _rights;
|
||||
|
||||
@@ -79,6 +79,8 @@ constexpr auto kTransactionsLimit = 100;
|
||||
.credits = tl.data().vstars().v,
|
||||
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
|
||||
.barePeerId = barePeerId,
|
||||
.bareGiveawayMsgId = uint64(
|
||||
tl.data().vgiveaway_post_id().value_or_empty()),
|
||||
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Peer;
|
||||
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
||||
@@ -215,6 +217,10 @@ rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() {
|
||||
};
|
||||
}
|
||||
|
||||
Data::CreditTopupOptions CreditsTopupOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
CreditsStatus::CreditsStatus(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().api().instance()) {
|
||||
@@ -294,10 +300,6 @@ void CreditsHistory::requestSubscriptions(
|
||||
}).send();
|
||||
}
|
||||
|
||||
Data::CreditTopupOptions CreditsTopupOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
rpl::producer<not_null<PeerData*>> PremiumPeerBot(
|
||||
not_null<Main::Session*> session) {
|
||||
const auto username = session->appConfig().get<QString>(
|
||||
@@ -385,4 +387,58 @@ Data::CreditsEarnStatistics CreditsEarnStatistics::data() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
CreditsGiveawayOptions::CreditsGiveawayOptions(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().api().instance()) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> CreditsGiveawayOptions::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
using TLOption = MTPStarsGiveawayOption;
|
||||
|
||||
const auto optionsFromTL = [=](const auto &options) {
|
||||
return ranges::views::all(
|
||||
options
|
||||
) | ranges::views::transform([=](const auto &option) {
|
||||
return Data::CreditsGiveawayOption{
|
||||
.winners = ranges::views::all(
|
||||
option.data().vwinners().v
|
||||
) | ranges::views::transform([](const auto &winner) {
|
||||
return Data::CreditsGiveawayOption::Winner{
|
||||
.users = winner.data().vusers().v,
|
||||
.perUserStars = winner.data().vper_user_stars().v,
|
||||
.isDefault = winner.data().is_default(),
|
||||
};
|
||||
}) | ranges::to_vector,
|
||||
.storeProduct = qs(
|
||||
option.data().vstore_product().value_or_empty()),
|
||||
.currency = qs(option.data().vcurrency()),
|
||||
.amount = option.data().vamount().v,
|
||||
.credits = option.data().vstars().v,
|
||||
.yearlyBoosts = option.data().vyearly_boosts().v,
|
||||
.isExtended = option.data().is_extended(),
|
||||
.isDefault = option.data().is_default(),
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
};
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
};
|
||||
|
||||
_api.request(MTPpayments_GetStarsGiveawayOptions(
|
||||
)).done([=](const MTPVector<TLOption> &result) {
|
||||
_options = optionsFromTL(result.v);
|
||||
consumer.put_done();
|
||||
}).fail(fail).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
Data::CreditsGiveawayOptions CreditsGiveawayOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -36,6 +36,22 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class CreditsGiveawayOptions final {
|
||||
public:
|
||||
CreditsGiveawayOptions(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
[[nodiscard]] Data::CreditsGiveawayOptions options() const;
|
||||
|
||||
private:
|
||||
const not_null<PeerData*> _peer;
|
||||
|
||||
Data::CreditsGiveawayOptions _options;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
class CreditsStatus final {
|
||||
public:
|
||||
CreditsStatus(not_null<PeerData*> peer);
|
||||
|
||||
@@ -115,6 +115,28 @@ rpl::producer<bool> GlobalPrivacy::newRequirePremium() const {
|
||||
return _newRequirePremium.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::loadPaidReactionAnonymous() {
|
||||
if (_paidReactionAnonymousLoaded) {
|
||||
return;
|
||||
}
|
||||
_paidReactionAnonymousLoaded = true;
|
||||
_api.request(MTPmessages_GetPaidReactionPrivacy(
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_session->api().applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updatePaidReactionAnonymous(bool value) {
|
||||
_paidReactionAnonymous = value;
|
||||
}
|
||||
|
||||
bool GlobalPrivacy::paidReactionAnonymousCurrent() const {
|
||||
return _paidReactionAnonymous.current();
|
||||
}
|
||||
|
||||
rpl::producer<bool> GlobalPrivacy::paidReactionAnonymous() const {
|
||||
return _paidReactionAnonymous.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updateArchiveAndMute(bool value) {
|
||||
update(
|
||||
|
||||
@@ -49,6 +49,11 @@ public:
|
||||
[[nodiscard]] bool newRequirePremiumCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> newRequirePremium() const;
|
||||
|
||||
void loadPaidReactionAnonymous();
|
||||
void updatePaidReactionAnonymous(bool value);
|
||||
[[nodiscard]] bool paidReactionAnonymousCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> paidReactionAnonymous() const;
|
||||
|
||||
private:
|
||||
void apply(const MTPGlobalPrivacySettings &data);
|
||||
|
||||
@@ -67,7 +72,9 @@ private:
|
||||
rpl::variable<bool> _showArchiveAndMute = false;
|
||||
rpl::variable<bool> _hideReadTime = false;
|
||||
rpl::variable<bool> _newRequirePremium = false;
|
||||
rpl::variable<bool> _paidReactionAnonymous = false;
|
||||
std::vector<Fn<void()>> _callbacks;
|
||||
bool _paidReactionAnonymousLoaded = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -361,9 +361,10 @@ void Premium::resolveGiveawayInfo(
|
||||
? GiveawayState::Refunded
|
||||
: GiveawayState::Finished;
|
||||
info.giftCode = qs(data.vgift_code_slug().value_or_empty());
|
||||
info.activatedCount = data.vactivated_count().v;
|
||||
info.activatedCount = data.vactivated_count().value_or_empty();
|
||||
info.finishDate = data.vfinish_date().v;
|
||||
info.startDate = data.vstart_date().v;
|
||||
info.credits = data.vstars_prize().value_or_empty();
|
||||
});
|
||||
_giveawayInfoDone(std::move(info));
|
||||
}).fail([=] {
|
||||
@@ -508,7 +509,9 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::applyPrepaid(
|
||||
_api.request(MTPpayments_LaunchPrepaidGiveaway(
|
||||
_peer->input,
|
||||
MTP_long(prepaidId),
|
||||
Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
|
||||
invoice.creditsAmount
|
||||
? Payments::InvoiceCreditsGiveawayToTL(invoice)
|
||||
: Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_peer->session().api().applyUpdates(result);
|
||||
consumer.put_done();
|
||||
@@ -537,10 +540,10 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
|
||||
const auto token = Token{ users, months };
|
||||
const auto &store = _stores[token];
|
||||
return Payments::InvoicePremiumGiftCode{
|
||||
.randomId = randomId,
|
||||
.currency = _optionsForOnePerson.currency,
|
||||
.amount = store.amount,
|
||||
.storeProduct = store.product,
|
||||
.randomId = randomId,
|
||||
.amount = store.amount,
|
||||
.storeQuantity = store.quantity,
|
||||
.users = token.users,
|
||||
.months = token.months,
|
||||
|
||||
@@ -57,6 +57,7 @@ struct GiveawayInfo {
|
||||
TimeId tooEarlyDate = 0;
|
||||
TimeId finishDate = 0;
|
||||
TimeId startDate = 0;
|
||||
uint64 credits = 0;
|
||||
int winnersCount = 0;
|
||||
int activatedCount = 0;
|
||||
bool participating = false;
|
||||
|
||||
@@ -571,13 +571,22 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
|
||||
_boostStatus.prepaidGiveaway = ranges::views::all(
|
||||
data.vprepaid_giveaways()->v
|
||||
) | ranges::views::transform([](const MTPPrepaidGiveaway &r) {
|
||||
return Data::BoostPrepaidGiveaway{
|
||||
.months = r.data().vmonths().v,
|
||||
.id = r.data().vid().v,
|
||||
.quantity = r.data().vquantity().v,
|
||||
.date = QDateTime::fromSecsSinceEpoch(
|
||||
r.data().vdate().v),
|
||||
};
|
||||
return r.match([&](const MTPDprepaidGiveaway &data) {
|
||||
return Data::BoostPrepaidGiveaway{
|
||||
.date = base::unixtime::parse(data.vdate().v),
|
||||
.id = data.vid().v,
|
||||
.months = data.vmonths().v,
|
||||
.quantity = data.vquantity().v,
|
||||
};
|
||||
}, [&](const MTPDprepaidStarsGiveaway &data) {
|
||||
return Data::BoostPrepaidGiveaway{
|
||||
.date = base::unixtime::parse(data.vdate().v),
|
||||
.id = data.vid().v,
|
||||
.credits = data.vstars().v,
|
||||
.quantity = data.vquantity().v,
|
||||
.boosts = data.vboosts().v,
|
||||
};
|
||||
});
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
@@ -635,19 +644,21 @@ void Boosts::requestBoosts(
|
||||
}
|
||||
: Data::GiftCodeLink();
|
||||
list.push_back({
|
||||
data.is_gift(),
|
||||
data.is_giveaway(),
|
||||
data.is_unclaimed(),
|
||||
qs(data.vid()),
|
||||
data.vuser_id().value_or_empty(),
|
||||
data.vgiveaway_msg_id()
|
||||
.id = qs(data.vid()),
|
||||
.userId = UserId(data.vuser_id().value_or_empty()),
|
||||
.giveawayMessage = data.vgiveaway_msg_id()
|
||||
? FullMsgId{ _peer->id, data.vgiveaway_msg_id()->v }
|
||||
: FullMsgId(),
|
||||
QDateTime::fromSecsSinceEpoch(data.vdate().v),
|
||||
QDateTime::fromSecsSinceEpoch(data.vexpires().v),
|
||||
(data.vexpires().v - data.vdate().v) / kMonthsDivider,
|
||||
std::move(giftCodeLink),
|
||||
data.vmultiplier().value_or_empty(),
|
||||
.date = base::unixtime::parse(data.vdate().v),
|
||||
.expiresAt = base::unixtime::parse(data.vexpires().v),
|
||||
.expiresAfterMonths = ((data.vexpires().v - data.vdate().v)
|
||||
/ kMonthsDivider),
|
||||
.giftCodeLink = std::move(giftCodeLink),
|
||||
.multiplier = data.vmultiplier().value_or_empty(),
|
||||
.credits = data.vstars().value_or_empty(),
|
||||
.isGift = data.is_gift(),
|
||||
.isGiveaway = data.is_giveaway(),
|
||||
.isUnclaimed = data.is_unclaimed(),
|
||||
});
|
||||
}
|
||||
done(Data::BoostsListSlice{
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_authorizations.h"
|
||||
#include "api/api_user_names.h"
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "api/api_ringtones.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "api/api_user_privacy.h"
|
||||
@@ -2622,6 +2623,12 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
_session->credits().apply(data);
|
||||
} break;
|
||||
|
||||
case mtpc_updatePaidReactionPrivacy: {
|
||||
const auto &data = update.c_updatePaidReactionPrivacy();
|
||||
_session->api().globalPrivacy().updatePaidReactionAnonymous(
|
||||
mtpIsTrue(data.vprivate()));
|
||||
} break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4160,8 +4160,10 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
(options.price
|
||||
? MTPInputMedia(MTP_inputMediaPaidMedia(
|
||||
MTP_flags(0),
|
||||
MTP_long(options.price),
|
||||
MTP_vector<MTPInputMedia>(1, media)))
|
||||
MTP_vector<MTPInputMedia>(1, media),
|
||||
MTPstring()))
|
||||
: media),
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
@@ -4232,8 +4234,10 @@ void ApiWrap::sendMultiPaidMedia(
|
||||
peer->input,
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
MTP_inputMediaPaidMedia(
|
||||
MTP_flags(0),
|
||||
MTP_long(options.price),
|
||||
MTP_vector<MTPInputMedia>(std::move(medias))),
|
||||
MTP_vector<MTPInputMedia>(std::move(medias)),
|
||||
MTPstring()),
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
|
||||
@@ -217,7 +217,9 @@ void ShowAddParticipantsError(
|
||||
channel,
|
||||
user,
|
||||
ChatAdminRightsInfo(),
|
||||
QString());
|
||||
QString(),
|
||||
0,
|
||||
nullptr);
|
||||
box->setSaveCallback(saveCallback);
|
||||
*weak = box.data();
|
||||
show->showBox(std::move(box));
|
||||
|
||||
@@ -1114,7 +1114,7 @@ QString FilterChatStatusText(not_null<PeerData*> peer) {
|
||||
? tr::lng_chat_status_subscribers
|
||||
: tr::lng_chat_status_members)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
lt_count_decimal,
|
||||
channel->membersCount());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ void GiftCreditsBox(
|
||||
) | rpl::map([](TextWithEntities text) {
|
||||
return Ui::Text::Link(
|
||||
std::move(text),
|
||||
tr::lng_credits_box_history_entry_gift_about_url(tr::now));
|
||||
u"internal:stars_examples"_q);
|
||||
});
|
||||
content->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
|
||||
@@ -69,6 +69,24 @@ constexpr auto kUserpicsMax = size_t(3);
|
||||
using GiftOption = Data::PremiumSubscriptionOption;
|
||||
using GiftOptions = Data::PremiumSubscriptionOptions;
|
||||
|
||||
[[nodiscard]] QString CreateMessageLink(
|
||||
not_null<Main::Session*> session,
|
||||
PeerId peerId,
|
||||
uint64 messageId) {
|
||||
if (const auto msgId = MsgId(peerId ? messageId : 0)) {
|
||||
const auto peer = session->data().peer(peerId);
|
||||
if (const auto channel = peer->asBroadcast()) {
|
||||
const auto username = channel->username();
|
||||
const auto base = username.isEmpty()
|
||||
? u"c/%1"_q.arg(peerToChannel(channel->id).bare)
|
||||
: username;
|
||||
const auto query = base + '/' + QString::number(msgId.bare);
|
||||
return session->createInternalLink(query);
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
};
|
||||
|
||||
GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
|
||||
auto result = GiftOptions();
|
||||
const auto gifts = data.vpremium_gifts();
|
||||
@@ -1419,21 +1437,56 @@ void GiveawayInfoBox(
|
||||
: !start->channels.empty()
|
||||
? start->channels.front()->name()
|
||||
: u"channel"_q;
|
||||
auto text = TextWithEntities();
|
||||
|
||||
if (!info.giftCode.isEmpty()) {
|
||||
text.append("\n\n");
|
||||
text.append(Ui::Text::Bold(tr::lng_prizes_you_won(
|
||||
tr::now,
|
||||
auto resultText = (!info.giftCode.isEmpty())
|
||||
? tr::lng_prizes_you_won(
|
||||
lt_cup,
|
||||
QString::fromUtf8("\xf0\x9f\x8f\x86"))));
|
||||
text.append("\n\n");
|
||||
} else if (info.state == State::Finished) {
|
||||
text.append("\n\n");
|
||||
text.append(Ui::Text::Bold(tr::lng_prizes_you_didnt(tr::now)));
|
||||
text.append("\n\n");
|
||||
rpl::single(
|
||||
TextWithEntities{ QString::fromUtf8("\xf0\x9f\x8f\x86") }),
|
||||
Ui::Text::WithEntities)
|
||||
: (info.credits)
|
||||
? tr::lng_prizes_you_won_credits(
|
||||
lt_amount,
|
||||
tr::lng_prizes_you_won_credits_amount(
|
||||
lt_count,
|
||||
rpl::single(float64(info.credits)),
|
||||
Ui::Text::Bold),
|
||||
lt_cup,
|
||||
rpl::single(
|
||||
TextWithEntities{ QString::fromUtf8("\xf0\x9f\x8f\x86") }),
|
||||
Ui::Text::WithEntities)
|
||||
: (info.state == State::Finished)
|
||||
? tr::lng_prizes_you_didnt(Ui::Text::WithEntities)
|
||||
: (rpl::producer<TextWithEntities>)(nullptr);
|
||||
|
||||
if (resultText) {
|
||||
const auto &st = st::changePhoneDescription;
|
||||
const auto skip = st.style.font->height * 0.5;
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
box.get(),
|
||||
std::move(resultText),
|
||||
st);
|
||||
if ((!info.giftCode.isEmpty()) || info.credits) {
|
||||
label->setTextColorOverride(st::windowActiveTextFg->c);
|
||||
}
|
||||
const auto result = box->addRow(
|
||||
object_ptr<Ui::PaddingWrap<Ui::CenterWrap<Ui::FlatLabel>>>(
|
||||
box.get(),
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box.get(),
|
||||
std::move(label)),
|
||||
QMargins(0, skip, 0, skip)));
|
||||
result->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(result);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::boxDividerBg);
|
||||
p.drawRoundedRect(result->rect(), st::boxRadius, st::boxRadius);
|
||||
}, result->lifetime());
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
}
|
||||
|
||||
auto text = TextWithEntities();
|
||||
|
||||
const auto quantity = start
|
||||
? start->quantity
|
||||
: (results->winnersCount + results->unclaimedCount);
|
||||
@@ -1442,22 +1495,39 @@ void GiveawayInfoBox(
|
||||
? results->channel->isMegagroup()
|
||||
: (!start->channels.empty()
|
||||
&& start->channels.front()->isMegagroup());
|
||||
const auto credits = start
|
||||
? start->credits
|
||||
: (results ? results->credits : 0);
|
||||
text.append((finished
|
||||
? tr::lng_prizes_end_text
|
||||
: tr::lng_prizes_how_text)(
|
||||
tr::now,
|
||||
lt_admins,
|
||||
(group
|
||||
? tr::lng_prizes_admins_group
|
||||
: tr::lng_prizes_admins)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
quantity,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_duration,
|
||||
TextWithEntities{ GiftDuration(months) },
|
||||
Ui::Text::RichLangValue),
|
||||
credits
|
||||
? (group
|
||||
? tr::lng_prizes_credits_admins_group
|
||||
: tr::lng_prizes_credits_admins)(
|
||||
tr::now,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_amount,
|
||||
tr::lng_prizes_credits_admins_amount(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
float64(credits),
|
||||
Ui::Text::Bold),
|
||||
Ui::Text::RichLangValue)
|
||||
: (group
|
||||
? tr::lng_prizes_admins_group
|
||||
: tr::lng_prizes_admins)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
quantity,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_duration,
|
||||
TextWithEntities{ GiftDuration(months) },
|
||||
Ui::Text::RichLangValue),
|
||||
Ui::Text::RichLangValue));
|
||||
const auto many = start
|
||||
? (start->channels.size() > 1)
|
||||
@@ -1651,6 +1721,7 @@ void AddCreditsHistoryEntryTable(
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
const auto session = &controller->session();
|
||||
if (peerId) {
|
||||
auto text = entry.in
|
||||
? tr::lng_credits_box_history_entry_peer_in()
|
||||
@@ -1658,15 +1729,12 @@ void AddCreditsHistoryEntryTable(
|
||||
AddTableRow(table, std::move(text), controller, peerId);
|
||||
}
|
||||
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
|
||||
const auto session = &controller->session();
|
||||
const auto peer = session->data().peer(peerId);
|
||||
if (const auto channel = peer->asBroadcast()) {
|
||||
const auto username = channel->username();
|
||||
const auto base = username.isEmpty()
|
||||
? u"c/%1"_q.arg(peerToChannel(channel->id).bare)
|
||||
: username;
|
||||
const auto query = base + '/' + QString::number(msgId.bare);
|
||||
const auto link = session->createInternalLink(query);
|
||||
const auto link = CreateMessageLink(
|
||||
session,
|
||||
peerId,
|
||||
entry.bareMsgId);
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
rpl::single(Ui::Text::Link(link)),
|
||||
@@ -1717,6 +1785,37 @@ void AddCreditsHistoryEntryTable(
|
||||
tr::lng_credits_box_history_entry_via_premium_bot(
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
if (entry.bareGiveawayMsgId) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_to(),
|
||||
controller,
|
||||
controller->session().userId());
|
||||
}
|
||||
if (entry.bareGiveawayMsgId && entry.credits) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_gift(),
|
||||
tr::lng_gift_stars_title(
|
||||
lt_count,
|
||||
rpl::single(float64(entry.credits)),
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
{
|
||||
const auto link = CreateMessageLink(
|
||||
session,
|
||||
peerId,
|
||||
entry.bareGiveawayMsgId);
|
||||
if (!link.isEmpty()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_reason(),
|
||||
tr::lng_gift_link_reason_giveaway(
|
||||
) | rpl::map([link](const QString &text) {
|
||||
return Ui::Text::Link(text, link);
|
||||
}));
|
||||
}
|
||||
}
|
||||
if (!entry.id.isEmpty()) {
|
||||
constexpr auto kOneLineCount = 18;
|
||||
const auto oneLine = entry.id.length() <= kOneLineCount;
|
||||
@@ -1813,3 +1912,60 @@ void AddSubscriberEntryTable(
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(d))));
|
||||
}
|
||||
}
|
||||
|
||||
void AddCreditsBoostTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::Boost &b) {
|
||||
auto table = container->add(
|
||||
object_ptr<Ui::TableLayout>(
|
||||
container,
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = b.giveawayMessage.peer;
|
||||
if (!peerId) {
|
||||
return;
|
||||
}
|
||||
const auto from = controller->session().data().peer(peerId);
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer_in(),
|
||||
controller,
|
||||
from->id);
|
||||
if (b.credits) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_gift(),
|
||||
tr::lng_gift_stars_title(
|
||||
lt_count,
|
||||
rpl::single(float64(b.credits)),
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
{
|
||||
const auto link = CreateMessageLink(
|
||||
&controller->session(),
|
||||
peerId,
|
||||
b.giveawayMessage.msg.bare);
|
||||
if (!link.isEmpty()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_reason(),
|
||||
tr::lng_gift_link_reason_giveaway(
|
||||
) | rpl::map([link](const QString &text) {
|
||||
return Ui::Text::Link(text, link);
|
||||
}));
|
||||
}
|
||||
}
|
||||
if (!b.date.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_date(),
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(b.date))));
|
||||
}
|
||||
if (!b.expiresAt.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_until(),
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(b.expiresAt))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ struct GiftCode;
|
||||
} // namespace Api
|
||||
|
||||
namespace Data {
|
||||
struct Boost;
|
||||
struct CreditsHistoryEntry;
|
||||
struct GiveawayStart;
|
||||
struct GiveawayResults;
|
||||
@@ -89,3 +90,8 @@ void AddSubscriberEntryTable(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
TimeId date);
|
||||
|
||||
void AddCreditsBoostTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::Boost &boost);
|
||||
|
||||
@@ -170,11 +170,15 @@ void AddBotToGroupBoxController::requestExistingRights(
|
||||
channel);
|
||||
_existingRights = participant.rights().flags;
|
||||
_existingRank = participant.rank();
|
||||
_promotedSince = participant.promotedSince();
|
||||
_promotedBy = participant.by();
|
||||
addBotToGroup(_existingRightsChannel);
|
||||
});
|
||||
}).fail([=] {
|
||||
_existingRights = ChatAdminRights();
|
||||
_existingRank = QString();
|
||||
_promotedSince = 0;
|
||||
_promotedBy = 0;
|
||||
addBotToGroup(_existingRightsChannel);
|
||||
}).send();
|
||||
}
|
||||
@@ -191,6 +195,8 @@ void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) {
|
||||
_existingRights = {};
|
||||
_existingRank = QString();
|
||||
_existingRightsChannel = nullptr;
|
||||
_promotedSince = 0;
|
||||
_promotedBy = 0;
|
||||
_bot->session().api().request(_existingRightsRequestId).cancel();
|
||||
}
|
||||
const auto requestedAddAdmin = (_scope == Scope::GroupAdmin)
|
||||
@@ -241,9 +247,12 @@ void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) {
|
||||
bot,
|
||||
ChatAdminRightsInfo(rights),
|
||||
_existingRank,
|
||||
_promotedSince,
|
||||
_promotedBy ? chat->owner().user(_promotedBy).get() : nullptr,
|
||||
EditAdminBotFields{
|
||||
_token,
|
||||
_existingRights.value_or(ChatAdminRights()) });
|
||||
_existingRights.value_or(ChatAdminRights()),
|
||||
});
|
||||
box->setSaveCallback(saveCallback);
|
||||
controller->show(std::move(box));
|
||||
} else {
|
||||
|
||||
@@ -65,6 +65,8 @@ private:
|
||||
mtpRequestId _existingRightsRequestId = 0;
|
||||
std::optional<ChatAdminRights> _existingRights;
|
||||
QString _existingRank;
|
||||
TimeId _promotedSince = 0;
|
||||
UserId _promotedBy = 0;
|
||||
|
||||
rpl::event_stream<not_null<PeerData*>> _groups;
|
||||
rpl::event_stream<not_null<PeerData*>> _channels;
|
||||
|
||||
@@ -1276,7 +1276,9 @@ void AddSpecialBoxController::showAdmin(
|
||||
_peer,
|
||||
user,
|
||||
currentRights,
|
||||
_additional.adminRank(user));
|
||||
_additional.adminRank(user),
|
||||
_additional.adminPromotedSince(user),
|
||||
_additional.adminPromotedBy(user));
|
||||
const auto show = delegate()->peerListUiShow();
|
||||
if (_additional.canAddOrEditAdmin(user)) {
|
||||
const auto done = crl::guard(this, [=](
|
||||
@@ -1354,7 +1356,9 @@ void AddSpecialBoxController::showRestricted(
|
||||
_peer,
|
||||
user,
|
||||
_additional.adminRights(user).has_value(),
|
||||
currentRights);
|
||||
currentRights,
|
||||
_additional.restrictedBy(user),
|
||||
_additional.restrictedSince(user));
|
||||
if (_additional.canRestrictParticipant(user)) {
|
||||
const auto done = crl::guard(this, [=](
|
||||
ChatRestrictionsInfo newRights) {
|
||||
|
||||
@@ -206,6 +206,8 @@ EditAdminBox::EditAdminBox(
|
||||
not_null<UserData*> user,
|
||||
ChatAdminRightsInfo rights,
|
||||
const QString &rank,
|
||||
TimeId promotedSince,
|
||||
UserData *by,
|
||||
std::optional<EditAdminBotFields> addingBot)
|
||||
: EditParticipantBox(
|
||||
nullptr,
|
||||
@@ -214,6 +216,8 @@ EditAdminBox::EditAdminBox(
|
||||
(rights.flags != 0))
|
||||
, _oldRights(rights)
|
||||
, _oldRank(rank)
|
||||
, _promotedSince(promotedSince)
|
||||
, _by(by)
|
||||
, _addingBot(std::move(addingBot)) {
|
||||
}
|
||||
|
||||
@@ -288,8 +292,26 @@ void EditAdminBox::prepare() {
|
||||
object_ptr<Ui::VerticalLayout>(this)));
|
||||
const auto inner = _adminControlsWrap->entity();
|
||||
|
||||
Ui::AddDivider(inner);
|
||||
Ui::AddSkip(inner);
|
||||
if (_promotedSince) {
|
||||
const auto parsed = base::unixtime::parse(_promotedSince);
|
||||
const auto label = Ui::AddDividerText(
|
||||
inner,
|
||||
tr::lng_rights_about_by(
|
||||
lt_user,
|
||||
rpl::single(_by
|
||||
? Ui::Text::Link(_by->name(), 1)
|
||||
: TextWithEntities{ QString::fromUtf8("\U0001F47B") }),
|
||||
lt_date,
|
||||
rpl::single(TextWithEntities{ langDateTimeFull(parsed) }),
|
||||
Ui::Text::WithEntities));
|
||||
if (_by) {
|
||||
label->setLink(1, _by->createOpenLink());
|
||||
}
|
||||
Ui::AddSkip(inner);
|
||||
} else {
|
||||
Ui::AddDivider(inner);
|
||||
Ui::AddSkip(inner);
|
||||
}
|
||||
|
||||
const auto chat = peer()->asChat();
|
||||
const auto channel = peer()->asChannel();
|
||||
@@ -356,17 +378,47 @@ void EditAdminBox::prepare() {
|
||||
) | rpl::then(std::move(
|
||||
changes
|
||||
));
|
||||
_aboutAddAdmins = inner->add(
|
||||
object_ptr<Ui::FlatLabel>(inner, st::boxDividerLabel),
|
||||
st::rightsAboutMargin);
|
||||
rpl::duplicate(
|
||||
selectedFlags
|
||||
) | rpl::map(
|
||||
(_1 & Flag::AddAdmins) != 0
|
||||
) | rpl::distinct_until_changed(
|
||||
) | rpl::start_with_next([=](bool checked) {
|
||||
refreshAboutAddAdminsText(checked);
|
||||
}, lifetime());
|
||||
|
||||
const auto hasRank = canSave() && (chat || channel->isMegagroup());
|
||||
|
||||
{
|
||||
const auto aboutAddAdminsInner = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
const auto emptyAboutAddAdminsInner = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
aboutAddAdminsInner->toggle(false, anim::type::instant);
|
||||
emptyAboutAddAdminsInner->toggle(false, anim::type::instant);
|
||||
Ui::AddSkip(emptyAboutAddAdminsInner->entity());
|
||||
if (hasRank) {
|
||||
Ui::AddDivider(emptyAboutAddAdminsInner->entity());
|
||||
Ui::AddSkip(emptyAboutAddAdminsInner->entity());
|
||||
}
|
||||
Ui::AddSkip(aboutAddAdminsInner->entity());
|
||||
Ui::AddDividerText(
|
||||
aboutAddAdminsInner->entity(),
|
||||
rpl::duplicate(
|
||||
selectedFlags
|
||||
) | rpl::map(
|
||||
(_1 & Flag::AddAdmins) != 0
|
||||
) | rpl::distinct_until_changed(
|
||||
) | rpl::map([=](bool canAddAdmins) -> rpl::producer<QString> {
|
||||
const auto empty = (amCreator() && user()->isSelf());
|
||||
aboutAddAdminsInner->toggle(!empty, anim::type::instant);
|
||||
emptyAboutAddAdminsInner->toggle(empty, anim::type::instant);
|
||||
if (empty) {
|
||||
return rpl::single(QString());
|
||||
} else if (!canSave()) {
|
||||
return tr::lng_rights_about_admin_cant_edit();
|
||||
} else if (canAddAdmins) {
|
||||
return tr::lng_rights_about_add_admins_yes();
|
||||
}
|
||||
return tr::lng_rights_about_add_admins_no();
|
||||
}) | rpl::flatten_latest());
|
||||
}
|
||||
|
||||
if (canTransferOwnership()) {
|
||||
const auto allFlags = AdminRightsForOwnershipTransfer(options);
|
||||
@@ -381,9 +433,7 @@ void EditAdminBox::prepare() {
|
||||
}
|
||||
|
||||
if (canSave()) {
|
||||
_rank = (chat || channel->isMegagroup())
|
||||
? addRankInput(inner).get()
|
||||
: nullptr;
|
||||
_rank = hasRank ? addRankInput(inner).get() : nullptr;
|
||||
_finishSave = [=, value = getChecked] {
|
||||
const auto newFlags = (value() | ChatAdminRight::Other)
|
||||
& ((!channel || channel->amCreator())
|
||||
@@ -449,7 +499,7 @@ void EditAdminBox::refreshButtons() {
|
||||
|
||||
not_null<Ui::InputField*> EditAdminBox::addRankInput(
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
Ui::AddDivider(container);
|
||||
// Ui::AddDivider(container);
|
||||
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
@@ -486,14 +536,13 @@ not_null<Ui::InputField*> EditAdminBox::addRankInput(
|
||||
}
|
||||
}, result->lifetime());
|
||||
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
tr::lng_rights_edit_admin_rank_about(
|
||||
lt_title,
|
||||
(isOwner ? tr::lng_owner_badge : tr::lng_admin_badge)()),
|
||||
st::boxDividerLabel),
|
||||
st::rightsAboutMargin);
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddDividerText(
|
||||
container,
|
||||
tr::lng_rights_edit_admin_rank_about(
|
||||
lt_title,
|
||||
(isOwner ? tr::lng_owner_badge : tr::lng_admin_badge)()));
|
||||
Ui::AddSkip(container);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -687,27 +736,18 @@ void EditAdminBox::sendTransferRequestFrom(
|
||||
})).handleFloodErrors().send();
|
||||
}
|
||||
|
||||
void EditAdminBox::refreshAboutAddAdminsText(bool canAddAdmins) {
|
||||
_aboutAddAdmins->setText([&] {
|
||||
if (amCreator() && user()->isSelf()) {
|
||||
return QString();
|
||||
} else if (!canSave()) {
|
||||
return tr::lng_rights_about_admin_cant_edit(tr::now);
|
||||
} else if (canAddAdmins) {
|
||||
return tr::lng_rights_about_add_admins_yes(tr::now);
|
||||
}
|
||||
return tr::lng_rights_about_add_admins_no(tr::now);
|
||||
}());
|
||||
}
|
||||
|
||||
EditRestrictedBox::EditRestrictedBox(
|
||||
QWidget*,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> user,
|
||||
bool hasAdminRights,
|
||||
ChatRestrictionsInfo rights)
|
||||
ChatRestrictionsInfo rights,
|
||||
UserData *by,
|
||||
TimeId since)
|
||||
: EditParticipantBox(nullptr, peer, user, hasAdminRights)
|
||||
, _oldRights(rights) {
|
||||
, _oldRights(rights)
|
||||
, _by(by)
|
||||
, _since(since) {
|
||||
}
|
||||
|
||||
void EditRestrictedBox::prepare() {
|
||||
@@ -782,6 +822,29 @@ void EditRestrictedBox::prepare() {
|
||||
// tr::lng_rights_chat_banned_block(tr::now),
|
||||
// st::boxLinkButton));
|
||||
|
||||
if (_since) {
|
||||
const auto parsed = base::unixtime::parse(_since);
|
||||
const auto inner = addControl(object_ptr<Ui::VerticalLayout>(this));
|
||||
const auto isBanned = (_oldRights.flags
|
||||
& ChatRestriction::ViewMessages);
|
||||
Ui::AddSkip(inner);
|
||||
const auto label = Ui::AddDividerText(
|
||||
inner,
|
||||
(isBanned
|
||||
? tr::lng_rights_chat_banned_by
|
||||
: tr::lng_rights_chat_restricted_by)(
|
||||
lt_user,
|
||||
rpl::single(_by
|
||||
? Ui::Text::Link(_by->name(), 1)
|
||||
: TextWithEntities{ QString::fromUtf8("\U0001F47B") }),
|
||||
lt_date,
|
||||
rpl::single(TextWithEntities{ langDateTimeFull(parsed) }),
|
||||
Ui::Text::WithEntities));
|
||||
if (_by) {
|
||||
label->setLink(1, _by->createOpenLink());
|
||||
}
|
||||
}
|
||||
|
||||
if (canSave()) {
|
||||
const auto save = [=, value = getRestrictions] {
|
||||
if (!_saveCallback) {
|
||||
|
||||
@@ -79,6 +79,8 @@ public:
|
||||
not_null<UserData*> user,
|
||||
ChatAdminRightsInfo rights,
|
||||
const QString &rank,
|
||||
TimeId promotedSince,
|
||||
UserData *by,
|
||||
std::optional<EditAdminBotFields> addingBot = {});
|
||||
|
||||
void setSaveCallback(
|
||||
@@ -110,7 +112,6 @@ private:
|
||||
}
|
||||
void finishAddAdmin();
|
||||
void refreshButtons();
|
||||
void refreshAboutAddAdminsText(bool canAddAdmins);
|
||||
bool canTransferOwnership() const;
|
||||
not_null<Ui::SlideWrap<Ui::RpWidget>*> setupTransferButton(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
@@ -127,11 +128,12 @@ private:
|
||||
Ui::Checkbox *_addAsAdmin = nullptr;
|
||||
Ui::SlideWrap<Ui::VerticalLayout> *_adminControlsWrap = nullptr;
|
||||
Ui::InputField *_rank = nullptr;
|
||||
QPointer<Ui::FlatLabel> _aboutAddAdmins;
|
||||
mtpRequestId _checkTransferRequestId = 0;
|
||||
mtpRequestId _transferRequestId = 0;
|
||||
Fn<void()> _save, _finishSave;
|
||||
|
||||
TimeId _promotedSince = 0;
|
||||
UserData *_by = nullptr;
|
||||
std::optional<EditAdminBotFields> _addingBot;
|
||||
|
||||
};
|
||||
@@ -146,7 +148,9 @@ public:
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> user,
|
||||
bool hasAdminRights,
|
||||
ChatRestrictionsInfo rights);
|
||||
ChatRestrictionsInfo rights,
|
||||
UserData *by,
|
||||
TimeId since);
|
||||
|
||||
void setSaveCallback(
|
||||
Fn<void(ChatRestrictionsInfo, ChatRestrictionsInfo)> callback) {
|
||||
@@ -170,6 +174,8 @@ private:
|
||||
TimeId getRealUntilValue() const;
|
||||
|
||||
const ChatRestrictionsInfo _oldRights;
|
||||
UserData *_by = nullptr;
|
||||
TimeId _since = 0;
|
||||
TimeId _until = 0;
|
||||
Fn<void(ChatRestrictionsInfo, ChatRestrictionsInfo)> _saveCallback;
|
||||
|
||||
|
||||
@@ -387,6 +387,24 @@ QString ParticipantsAdditionalData::adminRank(
|
||||
return (i != end(_adminRanks)) ? i->second : QString();
|
||||
}
|
||||
|
||||
TimeId ParticipantsAdditionalData::adminPromotedSince(
|
||||
not_null<UserData*> user) const {
|
||||
const auto i = _adminPromotedSince.find(user);
|
||||
return (i != end(_adminPromotedSince)) ? i->second : TimeId(0);
|
||||
}
|
||||
|
||||
TimeId ParticipantsAdditionalData::restrictedSince(
|
||||
not_null<PeerData*> peer) const {
|
||||
const auto i = _restrictedSince.find(peer);
|
||||
return (i != end(_restrictedSince)) ? i->second : TimeId(0);
|
||||
}
|
||||
|
||||
TimeId ParticipantsAdditionalData::memberSince(
|
||||
not_null<UserData*> user) const {
|
||||
const auto i = _memberSince.find(user);
|
||||
return (i != end(_memberSince)) ? i->second : TimeId(0);
|
||||
}
|
||||
|
||||
auto ParticipantsAdditionalData::restrictedRights(
|
||||
not_null<PeerData*> participant) const
|
||||
-> std::optional<ChatRestrictionsInfo> {
|
||||
@@ -689,6 +707,11 @@ UserData *ParticipantsAdditionalData::applyAdmin(
|
||||
} else {
|
||||
_adminRanks.remove(user);
|
||||
}
|
||||
if (data.promotedSince()) {
|
||||
_adminPromotedSince[user] = data.promotedSince();
|
||||
} else {
|
||||
_adminPromotedSince.remove(user);
|
||||
}
|
||||
if (const auto by = _peer->owner().userLoaded(data.by())) {
|
||||
const auto i = _adminPromotedBy.find(user);
|
||||
if (i == _adminPromotedBy.end()) {
|
||||
@@ -741,6 +764,11 @@ PeerData *ParticipantsAdditionalData::applyBanned(
|
||||
} else {
|
||||
_kicked.erase(participant);
|
||||
}
|
||||
if (data.restrictedSince()) {
|
||||
_restrictedSince[participant] = data.restrictedSince();
|
||||
} else {
|
||||
_restrictedSince.remove(participant);
|
||||
}
|
||||
_restrictedRights[participant] = data.restrictions();
|
||||
if (const auto by = _peer->owner().userLoaded(data.by())) {
|
||||
const auto i = _restrictedBy.find(participant);
|
||||
@@ -1720,7 +1748,9 @@ void ParticipantsBoxController::showAdmin(not_null<UserData*> user) {
|
||||
_peer,
|
||||
user,
|
||||
currentRights,
|
||||
_additional.adminRank(user));
|
||||
_additional.adminRank(user),
|
||||
_additional.adminPromotedSince(user),
|
||||
_additional.adminPromotedBy(user));
|
||||
if (_additional.canAddOrEditAdmin(user)) {
|
||||
const auto done = crl::guard(this, [=](
|
||||
ChatAdminRightsInfo newRights,
|
||||
@@ -1776,7 +1806,9 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
|
||||
_peer,
|
||||
user,
|
||||
hasAdminRights,
|
||||
currentRights);
|
||||
currentRights,
|
||||
_additional.restrictedBy(user),
|
||||
_additional.restrictedSince(user));
|
||||
if (_additional.canRestrictParticipant(user)) {
|
||||
const auto done = crl::guard(this, [=](
|
||||
ChatRestrictionsInfo newRights) {
|
||||
|
||||
@@ -106,14 +106,19 @@ public:
|
||||
not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] std::optional<ChatAdminRightsInfo> adminRights(
|
||||
not_null<UserData*> user) const;
|
||||
QString adminRank(not_null<UserData*> user) const;
|
||||
[[nodiscard]] QString adminRank(not_null<UserData*> user) const;
|
||||
[[nodiscard]] std::optional<ChatRestrictionsInfo> restrictedRights(
|
||||
not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] bool isCreator(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool isExternal(not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] bool isKicked(not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] UserData *adminPromotedBy(not_null<UserData*> user) const;
|
||||
[[nodiscard]] UserData *restrictedBy(not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] UserData *restrictedBy(
|
||||
not_null<PeerData*> participant) const;
|
||||
|
||||
[[nodiscard]] TimeId adminPromotedSince(not_null<UserData*>) const;
|
||||
[[nodiscard]] TimeId restrictedSince(not_null<PeerData*>) const;
|
||||
[[nodiscard]] TimeId memberSince(not_null<UserData*>) const;
|
||||
|
||||
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
|
||||
|
||||
@@ -144,6 +149,9 @@ private:
|
||||
// Data for channels.
|
||||
base::flat_map<not_null<UserData*>, ChatAdminRightsInfo> _adminRights;
|
||||
base::flat_map<not_null<UserData*>, QString> _adminRanks;
|
||||
base::flat_map<not_null<UserData*>, TimeId> _adminPromotedSince;
|
||||
base::flat_map<not_null<PeerData*>, TimeId> _restrictedSince;
|
||||
base::flat_map<not_null<UserData*>, TimeId> _memberSince;
|
||||
base::flat_set<not_null<UserData*>> _adminCanEdit;
|
||||
base::flat_map<not_null<UserData*>, not_null<UserData*>> _adminPromotedBy;
|
||||
std::map<not_null<PeerData*>, ChatRestrictionsInfo> _restrictedRights;
|
||||
|
||||
@@ -721,6 +721,7 @@ void PeerShortInfoBox::prepare() {
|
||||
_roundedTop.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
refreshRoundedTopImage(getDelegate()->style().bg->c);
|
||||
|
||||
setCustomCornersFilling(RectPart::FullTop);
|
||||
setDimensionsToContent(st::shortInfoWidth, _rows);
|
||||
}
|
||||
|
||||
@@ -795,10 +796,6 @@ void PeerShortInfoBox::prepareRows() {
|
||||
tr::lng_mediaview_copy(tr::now));
|
||||
}
|
||||
|
||||
RectParts PeerShortInfoBox::customCornersFilling() {
|
||||
return RectPart::FullTop;
|
||||
}
|
||||
|
||||
void PeerShortInfoBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
|
||||
|
||||
@@ -162,7 +162,6 @@ public:
|
||||
private:
|
||||
void prepare() override;
|
||||
void prepareRows();
|
||||
RectParts customCornersFilling() override;
|
||||
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ using Type = SelfDestructionBox::Type;
|
||||
|
||||
[[nodiscard]] std::vector<int> Values(Type type) {
|
||||
switch (type) {
|
||||
case Type::Account: return { 30, 90, 180, 365 };
|
||||
case Type::Account: return { 30, 90, 180, 365, 548, 720 };
|
||||
case Type::Sessions: return { 7, 30, 90, 180, 365 };
|
||||
}
|
||||
Unexpected("SelfDestructionBox::Type in Values.");
|
||||
@@ -113,8 +113,8 @@ void SelfDestructionBox::showContent() {
|
||||
QString SelfDestructionBox::DaysLabel(int days) {
|
||||
return !days
|
||||
? QString()
|
||||
: (days > 364)
|
||||
? tr::lng_years(tr::now, lt_count, days / 365)
|
||||
//: (days > 364)
|
||||
//? tr::lng_years(tr::now, lt_count, days / 365)
|
||||
: (days > 25)
|
||||
? tr::lng_months(tr::now, lt_count, std::max(days / 30, 1))
|
||||
: tr::lng_weeks(tr::now, lt_count, std::max(days / 7, 1));
|
||||
|
||||
@@ -129,7 +129,9 @@ namespace {
|
||||
not_null<Window::SessionController*> controller) {
|
||||
using Limit = HistoryView::Controls::CharactersLimitLabel;
|
||||
|
||||
const auto wrap = box->verticalLayout()->add(
|
||||
const auto bottomContainer = box->setPinnedToBottomContent(
|
||||
object_ptr<Ui::VerticalLayout>(box));
|
||||
const auto wrap = bottomContainer->add(
|
||||
object_ptr<Ui::RpWidget>(box),
|
||||
st::boxRowPadding);
|
||||
const auto input = Ui::CreateChild<Ui::InputField>(
|
||||
@@ -233,6 +235,7 @@ void SendGifWithCaptionBox(
|
||||
}
|
||||
box->setTitle(tr::lng_send_gif_with_caption());
|
||||
box->setWidth(st::boxWidth);
|
||||
box->getDelegate()->setStyle(st::sendGifBox);
|
||||
|
||||
const auto container = box->verticalLayout();
|
||||
[[maybe_unused]] const auto gifWidget = AddGifWidget(
|
||||
|
||||
@@ -1505,3 +1505,7 @@ pickLocationChooseOnMap: RoundButton(defaultActiveButton) {
|
||||
font: font(15px semibold);
|
||||
}
|
||||
}
|
||||
|
||||
sendGifBox: Box(defaultBox) {
|
||||
shadowIgnoreBottomSkip: true;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/update_checker.h"
|
||||
#include "core/application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "dialogs/ui/dialogs_suggestions.h"
|
||||
#include "boxes/background_preview_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/edit_birthday_box.h"
|
||||
@@ -923,6 +924,17 @@ bool ShowCollectibleUsername(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShowStarsExamples(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
controller->show(Dialogs::StarsExamplesBox(controller));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExportTestChatTheme(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<const Data::CloudTheme*> theme) {
|
||||
@@ -1380,6 +1392,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
|
||||
u"^collectible_username/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
|
||||
ShowCollectibleUsername,
|
||||
},
|
||||
{
|
||||
u"^stars_examples$"_q,
|
||||
ShowStarsExamples,
|
||||
},
|
||||
};
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 5004006;
|
||||
constexpr auto AppVersionStr = "5.4.6";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppVersion = 5005002;
|
||||
constexpr auto AppVersionStr = "5.5.2";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -27,10 +27,6 @@ struct GiftCodeLink final {
|
||||
};
|
||||
|
||||
struct Boost final {
|
||||
bool isGift = false;
|
||||
bool isGiveaway = false;
|
||||
bool isUnclaimed = false;
|
||||
|
||||
QString id;
|
||||
UserId userId = UserId(0);
|
||||
FullMsgId giveawayMessage;
|
||||
@@ -39,6 +35,11 @@ struct Boost final {
|
||||
int expiresAfterMonths = 0;
|
||||
GiftCodeLink giftCodeLink;
|
||||
int multiplier = 0;
|
||||
uint64 credits = 0;
|
||||
|
||||
bool isGift = false;
|
||||
bool isGiveaway = false;
|
||||
bool isUnclaimed = false;
|
||||
};
|
||||
|
||||
struct BoostsListSlice final {
|
||||
@@ -53,10 +54,12 @@ struct BoostsListSlice final {
|
||||
};
|
||||
|
||||
struct BoostPrepaidGiveaway final {
|
||||
int months = 0;
|
||||
uint64 id = 0;
|
||||
int quantity = 0;
|
||||
QDateTime date;
|
||||
uint64 id = 0;
|
||||
uint64 credits = 0;
|
||||
int months = 0;
|
||||
int quantity = 0;
|
||||
int boosts = 0;
|
||||
};
|
||||
|
||||
struct BoostStatus final {
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_channel.h"
|
||||
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel_admins.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -971,6 +972,9 @@ PeerId ChannelData::groupCallDefaultJoinAs() const {
|
||||
|
||||
void ChannelData::setAllowedReactions(Data::AllowedReactions value) {
|
||||
if (_allowedReactions != value) {
|
||||
if (value.paidEnabled) {
|
||||
session().api().globalPrivacy().loadPaidReactionAnonymous();
|
||||
}
|
||||
const auto enabled = [](const Data::AllowedReactions &allowed) {
|
||||
return (allowed.type != Data::AllowedReactionsType::Some)
|
||||
|| !allowed.some.empty()
|
||||
|
||||
@@ -57,6 +57,7 @@ struct CreditsHistoryEntry final {
|
||||
uint64 credits = 0;
|
||||
uint64 bareMsgId = 0;
|
||||
uint64 barePeerId = 0;
|
||||
uint64 bareGiveawayMsgId = 0;
|
||||
PeerType peerType;
|
||||
QDateTime subscriptionUntil;
|
||||
QDateTime successDate;
|
||||
@@ -80,4 +81,22 @@ struct CreditsStatusSlice final {
|
||||
OffsetToken tokenSubscriptions;
|
||||
};
|
||||
|
||||
struct CreditsGiveawayOption final {
|
||||
struct Winner final {
|
||||
int users = 0;
|
||||
uint64 perUserStars = 0;
|
||||
bool isDefault = false;
|
||||
};
|
||||
std::vector<Winner> winners;
|
||||
QString storeProduct;
|
||||
QString currency;
|
||||
uint64 amount = 0;
|
||||
uint64 credits = 0;
|
||||
int yearlyBoosts = 0;
|
||||
bool isExtended = false;
|
||||
bool isDefault = false;
|
||||
};
|
||||
|
||||
using CreditsGiveawayOptions = std::vector<CreditsGiveawayOption>;
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -189,7 +189,8 @@ rpl::producer<MessagesSlice> HistoryMessagesViewer(
|
||||
};
|
||||
const auto messageId = (aroundId.fullId.msg == ShowAtUnreadMsgId)
|
||||
? computeUnreadAroundId()
|
||||
: (aroundId.fullId.msg == ShowAtTheEndMsgId)
|
||||
: ((aroundId.fullId.msg == ShowAtTheEndMsgId)
|
||||
|| (aroundId == MaxMessagePosition))
|
||||
? (ServerMaxMsgId - 1)
|
||||
: (aroundId.fullId.peer == history->peer->id)
|
||||
? aroundId.fullId.msg
|
||||
|
||||
@@ -471,7 +471,8 @@ GiveawayStart ComputeGiveawayStartData(
|
||||
auto result = GiveawayStart{
|
||||
.untilDate = data.vuntil_date().v,
|
||||
.quantity = data.vquantity().v,
|
||||
.months = data.vmonths().v,
|
||||
.months = data.vmonths().value_or_empty(),
|
||||
.credits = data.vstars().value_or_empty(),
|
||||
.all = !data.is_only_new_subscribers(),
|
||||
};
|
||||
result.channels.reserve(data.vchannels().v.size());
|
||||
@@ -502,7 +503,8 @@ GiveawayResults ComputeGiveawayResultsData(
|
||||
.additionalPeersCount = additional.value_or_empty(),
|
||||
.winnersCount = data.vwinners_count().v,
|
||||
.unclaimedCount = data.vunclaimed_count().v,
|
||||
.months = data.vmonths().v,
|
||||
.months = data.vmonths().value_or_empty(),
|
||||
.credits = data.vstars().value_or_empty(),
|
||||
.refunded = data.is_refunded(),
|
||||
.all = !data.is_only_new_subscribers(),
|
||||
};
|
||||
|
||||
@@ -108,6 +108,7 @@ struct GiveawayStart {
|
||||
TimeId untilDate = 0;
|
||||
int quantity = 0;
|
||||
int months = 0;
|
||||
uint64 credits = 0;
|
||||
bool all = false;
|
||||
};
|
||||
|
||||
@@ -121,19 +122,21 @@ struct GiveawayResults {
|
||||
int winnersCount = 0;
|
||||
int unclaimedCount = 0;
|
||||
int months = 0;
|
||||
uint64 credits = 0;
|
||||
bool refunded = false;
|
||||
bool all = false;
|
||||
};
|
||||
|
||||
enum class GiftType : uchar {
|
||||
Premium, // count - months
|
||||
Stars, // count - stars
|
||||
Credits, // count - credits
|
||||
};
|
||||
|
||||
struct GiftCode {
|
||||
QString slug;
|
||||
ChannelData *channel = nullptr;
|
||||
int count = 0;
|
||||
int giveawayMsgId = 0;
|
||||
GiftType type = GiftType::Premium;
|
||||
bool viaGiveaway = false;
|
||||
bool unclaimed = false;
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_message_reactions.h"
|
||||
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "core/application.h"
|
||||
#include "history/history.h"
|
||||
@@ -152,6 +153,10 @@ constexpr auto kPaidAccumulatePeriod = 5 * crl::time(1000) + 500;
|
||||
return (i != end(top)) && i->my;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<bool> MaybeAnonymous(uint32 privacySet, uint32 anonymous) {
|
||||
return privacySet ? (anonymous == 1) : std::optional<bool>();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PossibleItemReactionsRef LookupPossibleReactions(
|
||||
@@ -261,6 +266,15 @@ PossibleItemReactionsRef LookupPossibleReactions(
|
||||
}
|
||||
result.customAllowed = (allowed.type == AllowedReactionsType::All)
|
||||
&& premiumPossible;
|
||||
|
||||
const auto favoriteId = reactions->favoriteId();
|
||||
if (favoriteId.custom()
|
||||
&& result.customAllowed
|
||||
&& !ranges::contains(result.recent, favoriteId, &Reaction::id)) {
|
||||
if (const auto temp = reactions->lookupTemporary(favoriteId)) {
|
||||
result.recent.insert(begin(result.recent), temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!item->reactionsAreTags()) {
|
||||
const auto toFront = [&](Data::ReactionId id) {
|
||||
@@ -1725,6 +1739,7 @@ void Reactions::sendPaidPrivacyRequest(
|
||||
not_null<HistoryItem*> item,
|
||||
PaidReactionSend send) {
|
||||
Expects(!_sendingPaid.contains(item));
|
||||
Expects(send.anonymous.has_value());
|
||||
Expects(!send.count);
|
||||
|
||||
const auto id = item->fullId();
|
||||
@@ -1733,7 +1748,7 @@ void Reactions::sendPaidPrivacyRequest(
|
||||
MTPmessages_TogglePaidReactionPrivacy(
|
||||
item->history()->peer->input,
|
||||
MTP_int(id.msg),
|
||||
MTP_bool(send.anonymous))
|
||||
MTP_bool(*send.anonymous))
|
||||
).done([=] {
|
||||
if (const auto item = _owner->message(id)) {
|
||||
if (_sendingPaid.remove(item)) {
|
||||
@@ -1771,7 +1786,8 @@ void Reactions::sendPaidRequest(
|
||||
item->history()->peer->input,
|
||||
MTP_int(id.msg),
|
||||
MTP_int(send.count),
|
||||
MTP_long(randomId)
|
||||
MTP_long(randomId),
|
||||
MTP_bool(send.anonymous.value_or(false))
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
if (const auto item = _owner->message(id)) {
|
||||
if (_sendingPaid.remove(item)) {
|
||||
@@ -1819,9 +1835,13 @@ MessageReactions::~MessageReactions() {
|
||||
cancelScheduledPaid();
|
||||
if (const auto paid = _paid.get()) {
|
||||
if (paid->sending > 0) {
|
||||
finishPaidSending(
|
||||
{ int(paid->sending), (paid->sendingAnonymous == 1) },
|
||||
false);
|
||||
finishPaidSending({
|
||||
.count = int(paid->sending),
|
||||
.valid = true,
|
||||
.anonymous = MaybeAnonymous(
|
||||
paid->sendingPrivacySet,
|
||||
paid->sendingAnonymous),
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2182,7 +2202,9 @@ void MessageReactions::markRead() {
|
||||
}
|
||||
}
|
||||
|
||||
void MessageReactions::scheduleSendPaid(int count, bool anonymous) {
|
||||
void MessageReactions::scheduleSendPaid(
|
||||
int count,
|
||||
std::optional<bool> anonymous) {
|
||||
Expects(count >= 0);
|
||||
|
||||
if (!_paid) {
|
||||
@@ -2190,7 +2212,10 @@ void MessageReactions::scheduleSendPaid(int count, bool anonymous) {
|
||||
}
|
||||
_paid->scheduled += count;
|
||||
_paid->scheduledFlag = 1;
|
||||
_paid->scheduledAnonymous = anonymous ? 1 : 0;
|
||||
if (anonymous.has_value()) {
|
||||
_paid->scheduledAnonymous = anonymous.value_or(false) ? 1 : 0;
|
||||
_paid->scheduledPrivacySet = anonymous.has_value();
|
||||
}
|
||||
if (count > 0) {
|
||||
_item->history()->session().credits().lock(count);
|
||||
}
|
||||
@@ -2210,6 +2235,7 @@ void MessageReactions::cancelScheduledPaid() {
|
||||
_paid->scheduled = 0;
|
||||
_paid->scheduledFlag = 0;
|
||||
_paid->scheduledAnonymous = 0;
|
||||
_paid->scheduledPrivacySet = 0;
|
||||
}
|
||||
if (!_paid->sendingFlag && _paid->top.empty()) {
|
||||
_paid = nullptr;
|
||||
@@ -2224,13 +2250,17 @@ PaidReactionSend MessageReactions::startPaidSending() {
|
||||
_paid->sending = _paid->scheduled;
|
||||
_paid->sendingFlag = _paid->scheduledFlag;
|
||||
_paid->sendingAnonymous = _paid->scheduledAnonymous;
|
||||
_paid->sendingPrivacySet = _paid->scheduledPrivacySet;
|
||||
_paid->scheduled = 0;
|
||||
_paid->scheduledFlag = 0;
|
||||
_paid->scheduledAnonymous = 0;
|
||||
_paid->scheduledPrivacySet = 0;
|
||||
return {
|
||||
.count = int(_paid->sending),
|
||||
.valid = true,
|
||||
.anonymous = (_paid->sendingAnonymous == 1),
|
||||
.anonymous = MaybeAnonymous(
|
||||
_paid->sendingPrivacySet,
|
||||
_paid->sendingAnonymous),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2240,11 +2270,14 @@ void MessageReactions::finishPaidSending(
|
||||
Expects(_paid != nullptr);
|
||||
Expects(send.count == _paid->sending);
|
||||
Expects(send.valid == (_paid->sendingFlag == 1));
|
||||
Expects(send.anonymous == (_paid->sendingAnonymous == 1));
|
||||
Expects(send.anonymous == MaybeAnonymous(
|
||||
_paid->sendingPrivacySet,
|
||||
_paid->sendingAnonymous));
|
||||
|
||||
_paid->sending = 0;
|
||||
_paid->sendingFlag = 0;
|
||||
_paid->sendingAnonymous = 0;
|
||||
_paid->sendingPrivacySet = 0;
|
||||
if (!_paid->scheduledFlag && _paid->top.empty()) {
|
||||
_paid = nullptr;
|
||||
} else if (!send.count) {
|
||||
@@ -2282,12 +2315,13 @@ bool MessageReactions::localPaidAnonymous() const {
|
||||
return !entry.peer;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
const auto api = &_item->history()->session().api();
|
||||
return api->globalPrivacy().paidReactionAnonymousCurrent();
|
||||
};
|
||||
return _paid
|
||||
&& (_paid->scheduledFlag
|
||||
&& ((_paid->scheduledFlag && _paid->scheduledPrivacySet)
|
||||
? (_paid->scheduledAnonymous == 1)
|
||||
: _paid->sendingFlag
|
||||
: (_paid->sendingFlag && _paid->sendingPrivacySet)
|
||||
? (_paid->sendingAnonymous == 1)
|
||||
: minePaidAnonymous());
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ struct MyTagInfo {
|
||||
struct PaidReactionSend {
|
||||
int count = 0;
|
||||
bool valid = false;
|
||||
bool anonymous = false;
|
||||
std::optional<bool> anonymous = false;
|
||||
};
|
||||
|
||||
class Reactions final : private CustomEmojiManager::Listener {
|
||||
@@ -409,7 +409,7 @@ public:
|
||||
[[nodiscard]] bool hasUnread() const;
|
||||
void markRead();
|
||||
|
||||
void scheduleSendPaid(int count, bool anonymous);
|
||||
void scheduleSendPaid(int count, std::optional<bool> anonymous);
|
||||
[[nodiscard]] int scheduledPaid() const;
|
||||
void cancelScheduledPaid();
|
||||
|
||||
@@ -424,12 +424,14 @@ public:
|
||||
private:
|
||||
struct Paid {
|
||||
std::vector<TopPaid> top;
|
||||
uint32 scheduled: 30 = 0;
|
||||
uint32 scheduled: 29 = 0;
|
||||
uint32 scheduledFlag : 1 = 0;
|
||||
uint32 scheduledAnonymous : 1 = 0;
|
||||
uint32 sending : 30 = 0;
|
||||
uint32 scheduledPrivacySet : 1 = 0;
|
||||
uint32 sending : 29 = 0;
|
||||
uint32 sendingFlag : 1 = 0;
|
||||
uint32 sendingAnonymous : 1 = 0;
|
||||
uint32 sendingPrivacySet : 1 = 0;
|
||||
};
|
||||
const not_null<HistoryItem*> _item;
|
||||
|
||||
|
||||
@@ -446,7 +446,7 @@ private:
|
||||
class PopularAppsController final
|
||||
: public Suggestions::ObjectListController {
|
||||
public:
|
||||
explicit PopularAppsController(
|
||||
PopularAppsController(
|
||||
not_null<Window::SessionController*> window,
|
||||
Fn<bool(not_null<PeerData*>)> filterOut,
|
||||
rpl::producer<> filterOutRefreshes);
|
||||
@@ -1127,7 +1127,9 @@ PopularAppsController::PopularAppsController(
|
||||
}
|
||||
|
||||
void PopularAppsController::prepare() {
|
||||
setupPlainDivider(tr::lng_bot_apps_popular());
|
||||
if (_filterOut) {
|
||||
setupPlainDivider(tr::lng_bot_apps_popular());
|
||||
}
|
||||
rpl::single() | rpl::then(
|
||||
std::move(_filterOutRefreshes)
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -1163,10 +1165,11 @@ void PopularAppsController::fill() {
|
||||
|
||||
void PopularAppsController::appendRow(not_null<UserData*> bot) {
|
||||
auto row = std::make_unique<PeerListRow>(bot);
|
||||
//if (const auto count = bot->botInfo->activeUsers) {
|
||||
// row->setCustomStatus(
|
||||
// tr::lng_bot_status_users(tr::now, lt_count_decimal, count));
|
||||
//}
|
||||
if (bot->isBot()) {
|
||||
if (!bot->botInfo->activeUsers && !bot->username().isEmpty()) {
|
||||
row->setCustomStatus('@' + bot->username());
|
||||
}
|
||||
}
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
|
||||
@@ -2283,4 +2286,40 @@ RecentPeersList RecentPeersContent(not_null<Main::Session*> session) {
|
||||
return RecentPeersList{ session->recentPeers().list() };
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> StarsExamplesBox(
|
||||
not_null<Window::SessionController*> window) {
|
||||
auto controller = std::make_unique<PopularAppsController>(
|
||||
window,
|
||||
nullptr,
|
||||
nullptr);
|
||||
const auto raw = controller.get();
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->setTitle(tr::lng_credits_box_history_entry_gift_examples());
|
||||
box->addButton(tr::lng_close(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
|
||||
raw->load();
|
||||
raw->chosen() | rpl::start_with_next([=](not_null<PeerData*> peer) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
if (const auto info = user->botInfo.get()) {
|
||||
if (info->hasMainApp) {
|
||||
window->session().attachWebView().open({
|
||||
.bot = user,
|
||||
.context = {
|
||||
.controller = window,
|
||||
.maySkipConfirmation = true,
|
||||
},
|
||||
.source = InlineBots::WebViewSourceBotProfile(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
window->showPeerInfo(peer);
|
||||
}, box->lifetime());
|
||||
};
|
||||
return Box<PeerListBox>(std::move(controller), std::move(initBox));
|
||||
}
|
||||
|
||||
} // namespace Dialogs
|
||||
|
||||
@@ -23,6 +23,7 @@ class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
class BoxContent;
|
||||
class ElasticScroll;
|
||||
class SettingsSlider;
|
||||
class VerticalLayout;
|
||||
@@ -215,4 +216,7 @@ private:
|
||||
[[nodiscard]] RecentPeersList RecentPeersContent(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> StarsExamplesBox(
|
||||
not_null<Window::SessionController*> window);
|
||||
|
||||
} // namespace Dialogs
|
||||
|
||||
@@ -733,12 +733,47 @@ Poll ParsePoll(const MTPDmessageMediaPoll &data) {
|
||||
GiveawayStart ParseGiveaway(const MTPDmessageMediaGiveaway &data) {
|
||||
auto result = GiveawayStart{
|
||||
.untilDate = data.vuntil_date().v,
|
||||
.credits = data.vstars().value_or_empty(),
|
||||
.quantity = data.vquantity().v,
|
||||
.months = data.vmonths().v,
|
||||
.months = data.vmonths().value_or_empty(),
|
||||
.all = !data.is_only_new_subscribers(),
|
||||
};
|
||||
for (const auto &id : data.vchannels().v) {
|
||||
result.channels.push_back(ChannelId(id));
|
||||
}
|
||||
if (const auto countries = data.vcountries_iso2()) {
|
||||
result.countries.reserve(countries->v.size());
|
||||
for (const auto &country : countries->v) {
|
||||
result.countries.push_back(qs(country));
|
||||
}
|
||||
}
|
||||
if (const auto additional = data.vprize_description()) {
|
||||
result.additionalPrize = qs(*additional);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
GiveawayResults ParseGiveaway(const MTPDmessageMediaGiveawayResults &data) {
|
||||
const auto additional = data.vadditional_peers_count();
|
||||
auto result = GiveawayResults{
|
||||
.channel = ChannelId(data.vchannel_id()),
|
||||
.untilDate = data.vuntil_date().v,
|
||||
.launchId = data.vlaunch_msg_id().v,
|
||||
.additionalPeersCount = additional.value_or_empty(),
|
||||
.winnersCount = data.vwinners_count().v,
|
||||
.unclaimedCount = data.vunclaimed_count().v,
|
||||
.months = data.vmonths().value_or_empty(),
|
||||
.credits = data.vstars().value_or_empty(),
|
||||
.refunded = data.is_refunded(),
|
||||
.all = !data.is_only_new_subscribers(),
|
||||
};
|
||||
result.winners.reserve(data.vwinners().v.size());
|
||||
for (const auto &id : data.vwinners().v) {
|
||||
result.winners.push_back(UserId(id));
|
||||
}
|
||||
if (const auto additional = data.vprize_description()) {
|
||||
result.additionalPrize = qs(*additional);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1246,7 +1281,7 @@ Media ParseMedia(
|
||||
}, [&](const MTPDmessageMediaGiveaway &data) {
|
||||
result.content = ParseGiveaway(data);
|
||||
}, [&](const MTPDmessageMediaGiveawayResults &data) {
|
||||
// #TODO export giveaway
|
||||
result.content = ParseGiveaway(data);
|
||||
}, [&](const MTPDmessageMediaPaidMedia &data) {
|
||||
result.content = ParsePaidMedia(context, data, folder, date);
|
||||
}, [](const MTPDmessageMediaEmpty &data) {});
|
||||
@@ -1508,6 +1543,7 @@ ServiceAction ParseServiceAction(
|
||||
auto content = ActionGiveawayResults();
|
||||
content.winners = data.vwinners_count().v;
|
||||
content.unclaimed = data.vunclaimed_count().v;
|
||||
content.credits = data.is_stars();
|
||||
result.content = content;
|
||||
}, [&](const MTPDmessageActionBoostApply &data) {
|
||||
auto content = ActionBoostApply();
|
||||
@@ -1527,8 +1563,16 @@ ServiceAction ParseServiceAction(
|
||||
content.cost = Ui::FillAmountAndCurrency(
|
||||
data.vamount().v,
|
||||
qs(data.vcurrency())).toUtf8();
|
||||
content.stars = data.vstars().v;
|
||||
content.credits = data.vstars().v;
|
||||
result.content = content;
|
||||
}, [&](const MTPDmessageActionPrizeStars &data) {
|
||||
result.content = ActionPrizeStars{
|
||||
.peerId = ParsePeerId(data.vboost_peer()),
|
||||
.amount = data.vstars().v,
|
||||
.transactionId = data.vtransaction_id().v,
|
||||
.giveawayMsgId = data.vgiveaway_msg_id().v,
|
||||
.isUnclaimed = data.is_unclaimed(),
|
||||
};
|
||||
}, [](const MTPDmessageActionEmpty &data) {});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -210,10 +210,29 @@ struct Poll {
|
||||
};
|
||||
|
||||
struct GiveawayStart {
|
||||
std::vector<QString> countries;
|
||||
std::vector<ChannelId> channels;
|
||||
QString additionalPrize;
|
||||
TimeId untilDate = 0;
|
||||
uint64 credits = 0;
|
||||
int quantity = 0;
|
||||
int months = 0;
|
||||
bool all = false;
|
||||
};
|
||||
|
||||
struct GiveawayResults {
|
||||
ChannelId channel = 0;
|
||||
std::vector<PeerId> winners;
|
||||
QString additionalPrize;
|
||||
TimeId untilDate = 0;
|
||||
int32 launchId = 0;
|
||||
int additionalPeersCount = 0;
|
||||
int winnersCount = 0;
|
||||
int unclaimedCount = 0;
|
||||
int months = 0;
|
||||
uint64 credits = 0;
|
||||
bool refunded = false;
|
||||
bool all = false;
|
||||
};
|
||||
|
||||
struct UserpicsSlice {
|
||||
@@ -349,6 +368,7 @@ struct Media {
|
||||
Invoice,
|
||||
Poll,
|
||||
GiveawayStart,
|
||||
GiveawayResults,
|
||||
PaidMedia,
|
||||
UnsupportedMedia> content;
|
||||
TimeId ttl = 0;
|
||||
@@ -570,6 +590,7 @@ struct ActionGiveawayLaunch {
|
||||
struct ActionGiveawayResults {
|
||||
int winners = 0;
|
||||
int unclaimed = 0;
|
||||
bool credits = false;
|
||||
};
|
||||
|
||||
struct ActionBoostApply {
|
||||
@@ -585,7 +606,15 @@ struct ActionPaymentRefunded {
|
||||
|
||||
struct ActionGiftStars {
|
||||
Utf8String cost;
|
||||
int stars = 0;
|
||||
int credits = 0;
|
||||
};
|
||||
|
||||
struct ActionPrizeStars {
|
||||
PeerId peerId = 0;
|
||||
uint64 amount = 0;
|
||||
Utf8String transactionId;
|
||||
int32 giveawayMsgId = 0;
|
||||
bool isUnclaimed = false;
|
||||
};
|
||||
|
||||
struct ServiceAction {
|
||||
@@ -631,7 +660,8 @@ struct ServiceAction {
|
||||
ActionGiveawayResults,
|
||||
ActionBoostApply,
|
||||
ActionPaymentRefunded,
|
||||
ActionGiftStars> content;
|
||||
ActionGiftStars,
|
||||
ActionPrizeStars> content;
|
||||
};
|
||||
|
||||
ServiceAction ParseServiceAction(
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "export/output/export_output_html.h"
|
||||
|
||||
#include "countries/countries_instance.h"
|
||||
#include "export/output/export_output_result.h"
|
||||
#include "export/data/export_data_types.h"
|
||||
#include "core/utils.h"
|
||||
@@ -597,7 +598,8 @@ private:
|
||||
const Data::Message &message,
|
||||
const QString &basePath,
|
||||
const PeersMap &peers,
|
||||
const QString &internalLinksDomain);
|
||||
const QString &internalLinksDomain,
|
||||
Fn<QByteArray(int messageId, QByteArray text)> wrapMessageLink);
|
||||
[[nodiscard]] QByteArray pushGenericMedia(const MediaData &data);
|
||||
[[nodiscard]] QByteArray pushStickerMedia(
|
||||
const Data::Document &data,
|
||||
@@ -615,6 +617,10 @@ private:
|
||||
[[nodiscard]] QByteArray pushGiveaway(
|
||||
const PeersMap &peers,
|
||||
const Data::GiveawayStart &data);
|
||||
[[nodiscard]] QByteArray pushGiveaway(
|
||||
const PeersMap &peers,
|
||||
const Data::GiveawayResults &data,
|
||||
Fn<QByteArray(int messageId, QByteArray text)> wrapMessageLink);
|
||||
|
||||
File _file;
|
||||
QByteArray _composedStart;
|
||||
@@ -1307,9 +1313,20 @@ auto HtmlWriter::Wrap::pushMessage(
|
||||
return serviceFrom + " just started a giveaway "
|
||||
"of Telegram Premium subscriptions to its followers.";
|
||||
}, [&](const ActionGiveawayResults &data) {
|
||||
return QByteArray::number(data.winners)
|
||||
+ " of the giveaway were randomly selected by Telegram "
|
||||
"and received private messages with giftcodes.";
|
||||
return !data.winners
|
||||
? "No winners of the giveaway could be selected."
|
||||
: (data.credits && data.unclaimed)
|
||||
? "Some winners of the giveaway were randomly selected by "
|
||||
"Telegram and received their prize."
|
||||
: (!data.credits && data.unclaimed)
|
||||
? "Some winners of the giveaway were randomly selected by "
|
||||
"Telegram and received private messages with giftcodes."
|
||||
: (data.credits && !data.unclaimed)
|
||||
? NumberToString(data.winners) + " of the giveaway was randomly "
|
||||
"selected by Telegram and received their prize."
|
||||
: NumberToString(data.winners) + " of the giveaway was randomly "
|
||||
"selected by Telegram and received private messages with "
|
||||
"giftcodes.";
|
||||
}, [&](const ActionBoostApply &data) {
|
||||
return serviceFrom
|
||||
+ " boosted the group "
|
||||
@@ -1322,14 +1339,20 @@ auto HtmlWriter::Wrap::pushMessage(
|
||||
+ amount;
|
||||
return result;
|
||||
}, [&](const ActionGiftStars &data) {
|
||||
if (!data.stars || data.cost.isEmpty()) {
|
||||
if (!data.credits || data.cost.isEmpty()) {
|
||||
return serviceFrom + " sent you a gift.";
|
||||
}
|
||||
return serviceFrom
|
||||
+ " sent you a gift for "
|
||||
+ data.cost
|
||||
+ ": "
|
||||
+ QString::number(data.stars).toUtf8()
|
||||
+ QString::number(data.credits).toUtf8()
|
||||
+ " Telegram Stars.";
|
||||
}, [&](const ActionPrizeStars &data) {
|
||||
return "You won a prize in a giveaway organized by "
|
||||
+ peers.wrapPeerName(data.peerId)
|
||||
+ ".\n Your prize is "
|
||||
+ QString::number(data.amount).toUtf8()
|
||||
+ " Telegram Stars.";
|
||||
}, [](v::null_t) { return QByteArray(); });
|
||||
|
||||
@@ -1451,7 +1474,13 @@ auto HtmlWriter::Wrap::pushMessage(
|
||||
block.append(popTag());
|
||||
}
|
||||
|
||||
block.append(pushMedia(message, basePath, peers, internalLinksDomain));
|
||||
block.append(
|
||||
pushMedia(
|
||||
message,
|
||||
basePath,
|
||||
peers,
|
||||
internalLinksDomain,
|
||||
wrapMessageLink));
|
||||
|
||||
const auto text = FormatText(message.text, internalLinksDomain, _base);
|
||||
if (!text.isEmpty()) {
|
||||
@@ -1554,7 +1583,8 @@ QByteArray HtmlWriter::Wrap::pushMedia(
|
||||
const Data::Message &message,
|
||||
const QString &basePath,
|
||||
const PeersMap &peers,
|
||||
const QString &internalLinksDomain) {
|
||||
const QString &internalLinksDomain,
|
||||
Fn<QByteArray(int messageId, QByteArray text)> wrapMessageLink) {
|
||||
const auto data = prepareMediaData(
|
||||
message,
|
||||
basePath,
|
||||
@@ -1582,6 +1612,8 @@ QByteArray HtmlWriter::Wrap::pushMedia(
|
||||
return pushPoll(*poll);
|
||||
} else if (const auto giveaway = std::get_if<GiveawayStart>(&content)) {
|
||||
return pushGiveaway(peers, *giveaway);
|
||||
} else if (const auto giveaway = std::get_if<GiveawayResults>(&content)) {
|
||||
return pushGiveaway(peers, *giveaway, wrapMessageLink);
|
||||
}
|
||||
Assert(v::is_null(content));
|
||||
return QByteArray();
|
||||
@@ -1910,17 +1942,47 @@ QByteArray HtmlWriter::Wrap::pushGiveaway(
|
||||
result.append(pushDiv("media_giveaway"));
|
||||
|
||||
result.append(pushDiv("section_title bold"));
|
||||
result.append(SerializeString("Giveaway Prizes"));
|
||||
result.append((data.quantity > 1)
|
||||
? SerializeString("Giveaway Prizes")
|
||||
: SerializeString("Giveaway Prize"));
|
||||
result.append(popTag());
|
||||
|
||||
{
|
||||
result.append(pushDiv("section_body"));
|
||||
result.append("<b>"
|
||||
+ Data::NumberToString(data.quantity)
|
||||
+ "</b> "
|
||||
+ SerializeString(data.additionalPrize.toUtf8()));
|
||||
result.append(popTag());
|
||||
result.append(pushDiv("section_title bold"));
|
||||
result.append(SerializeString("with"));
|
||||
result.append(popTag());
|
||||
};
|
||||
result.append(pushDiv("section_body"));
|
||||
result.append("<b>"
|
||||
+ Data::NumberToString(data.quantity)
|
||||
+ "</b> "
|
||||
+ SerializeString((data.quantity > 1)
|
||||
? "Telegram Premium Subscriptions"
|
||||
: "Telegram Premium Subscription")
|
||||
+ " for <b>" + Data::NumberToString(data.months) + "</b> "
|
||||
+ (data.months > 1 ? "months." : "month."));
|
||||
if (data.credits > 0) {
|
||||
result.append("<b>"
|
||||
+ Data::NumberToString(data.credits)
|
||||
+ (SerializeString(data.credits == 1 ? (" Star") : (" Stars")))
|
||||
+ "</b> " + SerializeString("will be distributed ")
|
||||
+ ((data.quantity == 1)
|
||||
? SerializeString("to ")
|
||||
+ "<b>"
|
||||
+ Data::NumberToString(data.quantity)
|
||||
+ "</b> " + SerializeString("winner.")
|
||||
: SerializeString("among ")
|
||||
+ "<b>"
|
||||
+ Data::NumberToString(data.quantity)
|
||||
+ "</b> " + SerializeString("winners.")));
|
||||
} else {
|
||||
result.append("<b>"
|
||||
+ Data::NumberToString(data.quantity)
|
||||
+ "</b> "
|
||||
+ SerializeString((data.quantity > 1)
|
||||
? "Telegram Premium Subscriptions"
|
||||
: "Telegram Premium Subscription")
|
||||
+ " for <b>" + Data::NumberToString(data.months) + "</b> "
|
||||
+ (data.months > 1 ? "months." : "month."));
|
||||
}
|
||||
result.append(popTag());
|
||||
|
||||
result.append(pushDiv("section_title bold"));
|
||||
@@ -1928,15 +1990,85 @@ QByteArray HtmlWriter::Wrap::pushGiveaway(
|
||||
result.append(popTag());
|
||||
result.append(pushDiv("section_body"));
|
||||
auto channels = QByteArrayList();
|
||||
auto anyChannel = false;
|
||||
auto anyGroup = false;
|
||||
for (const auto &channel : data.channels) {
|
||||
if (const auto chat = peers.peer(channel).chat()) {
|
||||
if (chat->isBroadcast) {
|
||||
anyChannel = true;
|
||||
} else if (chat->isSupergroup) {
|
||||
anyGroup = true;
|
||||
}
|
||||
}
|
||||
channels.append("<b>" + peers.wrapPeerName(channel) + "</b>");
|
||||
}
|
||||
result.append(SerializeString((channels.size() > 1)
|
||||
? "All subscribers of those channels: "
|
||||
: "All subscribers of the channel: ")
|
||||
+ channels.join(", "));
|
||||
|
||||
const auto participants = [&] {
|
||||
if (data.all && !anyGroup && anyChannel && channels.size() == 1) {
|
||||
return "All subscribers of the channel:";
|
||||
}
|
||||
if (data.all && !anyGroup && anyChannel && channels.size() > 1) {
|
||||
return "All subscribers of the channels:";
|
||||
}
|
||||
if (data.all && anyGroup && !anyChannel && channels.size() == 1) {
|
||||
return "All members of the group:";
|
||||
}
|
||||
if (data.all && anyGroup && !anyChannel && channels.size() > 1) {
|
||||
return "All members of the groups:";
|
||||
}
|
||||
if (data.all && anyGroup && anyChannel && channels.size() == 1) {
|
||||
return "All members of the group:";
|
||||
}
|
||||
if (data.all && anyGroup && anyChannel && channels.size() > 1) {
|
||||
return "All members of the groups and channels:";
|
||||
}
|
||||
if (!data.all && !anyGroup && anyChannel && channels.size() == 1) {
|
||||
return "All users who joined the channel below after this date:";
|
||||
}
|
||||
if (!data.all && !anyGroup && anyChannel && channels.size() > 1) {
|
||||
return "All users who joined the channels below after this date:";
|
||||
}
|
||||
if (!data.all && anyGroup && !anyChannel && channels.size() == 1) {
|
||||
return "All users who joined the group below after this date:";
|
||||
}
|
||||
if (!data.all && anyGroup && !anyChannel && channels.size() > 1) {
|
||||
return "All users who joined the groups below after this date:";
|
||||
}
|
||||
if (!data.all && anyGroup && anyChannel && channels.size() == 1) {
|
||||
return "All users who joined the group below after this date:";
|
||||
}
|
||||
if (!data.all && anyGroup && anyChannel && channels.size() > 1) {
|
||||
return "All users who joined the groups and channels below "
|
||||
"after this date:";
|
||||
}
|
||||
return "";
|
||||
}();
|
||||
|
||||
result.append(SerializeString(participants)) + channels.join(", ");
|
||||
result.append(popTag());
|
||||
|
||||
{
|
||||
const auto &instance = Countries::Instance();
|
||||
auto countries = QStringList();
|
||||
for (const auto &country : data.countries) {
|
||||
const auto name = instance.countryNameByISO2(country);
|
||||
const auto flag = instance.flagEmojiByISO2(country);
|
||||
countries.push_back(flag + QChar(0xA0) + name);
|
||||
}
|
||||
|
||||
if (const auto count = countries.size()) {
|
||||
auto united = countries.front();
|
||||
for (auto i = 1; i != count; ++i) {
|
||||
united = ((i + 1 == count)
|
||||
? u"%1 and %2"_q
|
||||
: u"%1, %2"_q).arg(united, countries[i]);
|
||||
}
|
||||
result.append(pushDiv("section_body"));
|
||||
result.append(
|
||||
SerializeString((u"from %1"_q).arg(united).toUtf8()));
|
||||
result.append(popTag());
|
||||
}
|
||||
}
|
||||
result.append(pushDiv("section_title bold"));
|
||||
result.append(SerializeString("Winners Selection Date"));
|
||||
result.append(popTag());
|
||||
@@ -1949,6 +2081,85 @@ QByteArray HtmlWriter::Wrap::pushGiveaway(
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray HtmlWriter::Wrap::pushGiveaway(
|
||||
const PeersMap &peers,
|
||||
const Data::GiveawayResults &data,
|
||||
Fn<QByteArray(int messageId, QByteArray text)> wrapMessageLink) {
|
||||
auto result = pushDiv("media_wrap clearfix");
|
||||
result.append(pushDiv("media_giveaway"));
|
||||
|
||||
result.append(pushDiv("section_title bold"));
|
||||
result.append((data.winnersCount > 1)
|
||||
? SerializeString("Winners Selected!")
|
||||
: SerializeString("Winner Selected!"));
|
||||
result.append(popTag());
|
||||
|
||||
result.append(pushDiv("section_body"));
|
||||
result.append(
|
||||
"<b>" + Data::NumberToString(data.winnersCount) + "</b> "
|
||||
+ SerializeString((data.winnersCount > 1) ? "winners" : "winner")
|
||||
+ " of the "
|
||||
+ wrapMessageLink(data.launchId, "Giveaway")
|
||||
+ " was randomly selected by Telegram.");
|
||||
result.append(popTag());
|
||||
|
||||
result.append(pushDiv("section_title bold"));
|
||||
result.append((data.winnersCount > 1)
|
||||
? SerializeString("Winners")
|
||||
: SerializeString("Winner"));
|
||||
result.append(popTag());
|
||||
|
||||
result.append(pushDiv("section_body"));
|
||||
auto winners = QByteArrayList();
|
||||
for (const auto &winner : data.winners) {
|
||||
winners.append("<b>" + peers.wrapPeerName(winner) + "</b>");
|
||||
}
|
||||
const auto andMore = [&, size = data.winners.size()] {
|
||||
if (data.winnersCount > size) {
|
||||
return SerializeString(" and ")
|
||||
+ Data::NumberToString(data.winnersCount - size)
|
||||
+ SerializeString(" more!");
|
||||
}
|
||||
return QByteArray();
|
||||
}();
|
||||
result.append(winners.join(", ") + andMore);
|
||||
result.append(popTag());
|
||||
|
||||
result.append(pushDiv("section_body"));
|
||||
const auto prize = [&, singleStar = (data.credits == 1)] {
|
||||
if (data.credits && data.winnersCount == 1) {
|
||||
return SerializeString("The winner received ")
|
||||
+ "<b>"
|
||||
+ Data::NumberToString(data.credits)
|
||||
+ "</b>"
|
||||
+ SerializeString(singleStar ? " Star." : " Stars.");
|
||||
} else if (data.credits && data.winnersCount > 1) {
|
||||
return SerializeString("All winners received ")
|
||||
+ "<b>"
|
||||
+ Data::NumberToString(data.credits)
|
||||
+ "</b>"
|
||||
+ SerializeString(singleStar
|
||||
? " Star in total."
|
||||
: " Stars in total.");
|
||||
} else if (data.unclaimedCount) {
|
||||
return SerializeString("Some winners couldn't be selected.");
|
||||
} else if (data.winnersCount == 1) {
|
||||
return SerializeString(
|
||||
"The winner received their gift link in a private message.");
|
||||
} else if (data.winnersCount > 1) {
|
||||
return SerializeString(
|
||||
"All winners received gift links in private messages.");
|
||||
}
|
||||
return QByteArray();
|
||||
}();
|
||||
result.append(prize);
|
||||
result.append(popTag());
|
||||
|
||||
result.append(popTag());
|
||||
result.append(popTag());
|
||||
return result;
|
||||
}
|
||||
|
||||
MediaData HtmlWriter::Wrap::prepareMediaData(
|
||||
const Data::Message &message,
|
||||
const QString &basePath,
|
||||
@@ -2108,6 +2319,7 @@ MediaData HtmlWriter::Wrap::prepareMediaData(
|
||||
result.status = Data::FormatMoneyAmount(data.amount, data.currency);
|
||||
}, [](const Poll &data) {
|
||||
}, [](const GiveawayStart &data) {
|
||||
}, [](const GiveawayResults &data) {
|
||||
}, [&](const PaidMedia &data) {
|
||||
result.classes = "media_invoice";
|
||||
result.status = Data::FormatMoneyAmount(data.stars, "XTR");
|
||||
|
||||
@@ -615,6 +615,7 @@ QByteArray SerializeMessage(
|
||||
pushAction("giveaway_results");
|
||||
push("winners", data.winners);
|
||||
push("unclaimed", data.unclaimed);
|
||||
push("stars", data.credits);
|
||||
}, [&](const ActionSetChatWallPaper &data) {
|
||||
pushActor();
|
||||
pushAction(data.same
|
||||
@@ -638,9 +639,18 @@ QByteArray SerializeMessage(
|
||||
if (!data.cost.isEmpty()) {
|
||||
push("cost", data.cost);
|
||||
}
|
||||
if (data.stars) {
|
||||
push("stars", data.stars);
|
||||
if (data.credits) {
|
||||
push("stars", data.credits);
|
||||
}
|
||||
}, [&](const ActionPrizeStars &data) {
|
||||
pushActor();
|
||||
pushAction("stars_prize");
|
||||
push("boost_peer_id", data.peerId);
|
||||
pushBare("boost_peer_name", wrapPeerName(data.peerId));
|
||||
push("stars", data.amount);
|
||||
push("is_unclaimed", data.isUnclaimed);
|
||||
push("giveaway_msg_id", data.giveawayMsgId);
|
||||
push("transaction_id", data.transactionId);
|
||||
}, [](v::null_t) {});
|
||||
|
||||
if (v::is_null(message.action.content)) {
|
||||
@@ -780,7 +790,7 @@ QByteArray SerializeMessage(
|
||||
{ "answers", serialized }
|
||||
}));
|
||||
}, [&](const GiveawayStart &data) {
|
||||
context.nesting.push_back(Context::kObject);
|
||||
context.nesting.push_back(Context::kArray);
|
||||
const auto channels = ranges::views::all(
|
||||
data.channels
|
||||
) | ranges::views::transform([&](ChannelId id) {
|
||||
@@ -789,11 +799,53 @@ QByteArray SerializeMessage(
|
||||
const auto serialized = SerializeArray(context, channels);
|
||||
context.nesting.pop_back();
|
||||
|
||||
push("giveaway_information", SerializeObject(context, {
|
||||
context.nesting.push_back(Context::kArray);
|
||||
const auto countries = ranges::views::all(
|
||||
data.countries
|
||||
) | ranges::views::transform([&](const QString &code) {
|
||||
return SerializeString(code.toUtf8());
|
||||
}) | ranges::to_vector;
|
||||
const auto serializedCountries = SerializeArray(context, countries);
|
||||
context.nesting.pop_back();
|
||||
|
||||
const auto additionalPrize = data.additionalPrize.toUtf8();
|
||||
|
||||
pushBare("giveaway_information", SerializeObject(context, {
|
||||
{ "quantity", NumberToString(data.quantity) },
|
||||
{ "months", NumberToString(data.months) },
|
||||
{ "until_date", SerializeDate(data.untilDate) },
|
||||
{ "channels", serialized },
|
||||
{ "countries", serializedCountries },
|
||||
{ "additional_prize", SerializeString(additionalPrize) },
|
||||
{ "stars", NumberToString(data.credits) },
|
||||
{ "is_only_new_subscribers", (!data.all) ? "true" : "false" },
|
||||
}));
|
||||
}, [&](const GiveawayResults &data) {
|
||||
context.nesting.push_back(Context::kArray);
|
||||
const auto winners = ranges::views::all(
|
||||
data.winners
|
||||
) | ranges::views::transform([&](PeerId id) {
|
||||
return NumberToString(id.value);
|
||||
}) | ranges::to_vector;
|
||||
const auto serialized = SerializeArray(context, winners);
|
||||
context.nesting.pop_back();
|
||||
|
||||
const auto additionalPrize = data.additionalPrize.toUtf8();
|
||||
const auto peersCount = data.additionalPeersCount;
|
||||
|
||||
pushBare("giveaway_results", SerializeObject(context, {
|
||||
{ "channel", NumberToString(data.channel.bare) },
|
||||
{ "winners", serialized },
|
||||
{ "additional_prize", SerializeString(additionalPrize) },
|
||||
{ "until_date", SerializeDate(data.untilDate) },
|
||||
{ "launch_message_id", NumberToString(data.launchId) },
|
||||
{ "additional_peers_count", NumberToString(peersCount) },
|
||||
{ "winners_count", NumberToString(data.winnersCount) },
|
||||
{ "unclaimed_count", NumberToString(data.unclaimedCount) },
|
||||
{ "months", NumberToString(data.months) },
|
||||
{ "stars", NumberToString(data.credits) },
|
||||
{ "is_refunded", data.refunded ? "true" : "false" },
|
||||
{ "is_only_new_subscribers", (!data.all) ? "true" : "false" },
|
||||
}));
|
||||
}, [&](const PaidMedia &data) {
|
||||
push("paid_stars_amount", data.stars);
|
||||
|
||||
@@ -50,6 +50,10 @@ EditFlagsDescriptor<FilterValue::Flags> FilterValueLabels(bool isChannel) {
|
||||
? tr::lng_admin_log_filter_voice_chats
|
||||
: tr::lng_admin_log_filter_voice_chats_channel)(tr::now),
|
||||
},
|
||||
{
|
||||
Flag::SubExtend,
|
||||
tr::lng_admin_log_filter_sub_extend(tr::now),
|
||||
},
|
||||
};
|
||||
if (!isChannel) {
|
||||
settings.push_back({
|
||||
|
||||
@@ -28,8 +28,9 @@ struct FilterValue final {
|
||||
GroupCall = (1U << 14),
|
||||
Invites = (1U << 15),
|
||||
Topics = (1U << 16),
|
||||
SubExtend = (1U << 17),
|
||||
|
||||
MAX_FIELD = (1U << 16),
|
||||
MAX_FIELD = (1U << 17),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr bool is_flag_type(Flag) { return true; };
|
||||
|
||||
@@ -572,11 +572,13 @@ void InnerWidget::showFilter(Fn<void(FilterValue &&filter)> callback) {
|
||||
const auto users = ranges::views::all(
|
||||
peers
|
||||
) | ranges::views::transform([](not_null<PeerData*> p) {
|
||||
return not_null{ p->asUser() };
|
||||
return not_null{ p->asUser() };
|
||||
}) | ranges::to_vector;
|
||||
callback(FilterValue{
|
||||
.flags = collectFlags(),
|
||||
.admins = users,
|
||||
.admins = (admins.size() == users.size())
|
||||
? std::nullopt
|
||||
: std::optional(users),
|
||||
});
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [box] { box->closeBox(); });
|
||||
@@ -835,7 +837,8 @@ void InnerWidget::preloadMore(Direction direction) {
|
||||
| ((f & LocalFlag::Delete) ? Flag::f_delete : empty)
|
||||
| ((f & LocalFlag::GroupCall) ? Flag::f_group_call : empty)
|
||||
| ((f & LocalFlag::Invites) ? Flag::f_invites : empty)
|
||||
| ((f & LocalFlag::Topics) ? Flag::f_forums : empty);
|
||||
| ((f & LocalFlag::Topics) ? Flag::f_forums : empty)
|
||||
| ((f & LocalFlag::SubExtend) ? Flag::f_sub_extend : empty);
|
||||
}();
|
||||
if (_filter.flags != 0) {
|
||||
flags |= MTPchannels_GetAdminLog::Flag::f_events_filter;
|
||||
@@ -1506,15 +1509,28 @@ void InnerWidget::suggestRestrictParticipant(
|
||||
}
|
||||
_menu->addAction(tr::lng_context_restrict_user(tr::now), [=] {
|
||||
const auto user = participant->asUser();
|
||||
auto editRestrictions = [=](bool hasAdminRights, ChatRestrictionsInfo currentRights) {
|
||||
auto editRestrictions = [=](
|
||||
bool hasAdminRights,
|
||||
ChatRestrictionsInfo currentRights,
|
||||
UserData *by,
|
||||
TimeId since) {
|
||||
auto weak = QPointer<InnerWidget>(this);
|
||||
auto weakBox = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||
auto box = Box<EditRestrictedBox>(_channel, user, hasAdminRights, currentRights);
|
||||
auto box = Box<EditRestrictedBox>(
|
||||
_channel,
|
||||
user,
|
||||
hasAdminRights,
|
||||
currentRights,
|
||||
by,
|
||||
since);
|
||||
box->setSaveCallback([=](
|
||||
ChatRestrictionsInfo oldRights,
|
||||
ChatRestrictionsInfo newRights) {
|
||||
if (weak) {
|
||||
weak->restrictParticipant(participant, oldRights, newRights);
|
||||
weak->restrictParticipant(
|
||||
participant,
|
||||
oldRights,
|
||||
newRights);
|
||||
}
|
||||
if (*weakBox) {
|
||||
(*weakBox)->closeBox();
|
||||
@@ -1541,29 +1557,30 @@ void InnerWidget::suggestRestrictParticipant(
|
||||
});
|
||||
*weakBox = _controller->show(Ui::MakeConfirmBox({ text, sure }));
|
||||
} else if (base::contains(_admins, user)) {
|
||||
editRestrictions(true, ChatRestrictionsInfo());
|
||||
editRestrictions(true, {}, nullptr, 0);
|
||||
} else {
|
||||
_api.request(MTPchannels_GetParticipant(
|
||||
_channel->inputChannel,
|
||||
user->input
|
||||
)).done([=](const MTPchannels_ChannelParticipant &result) {
|
||||
Expects(result.type() == mtpc_channels_channelParticipant);
|
||||
user->owner().processUsers(result.data().vusers());
|
||||
|
||||
auto &participant = result.c_channels_channelParticipant();
|
||||
_channel->owner().processUsers(participant.vusers());
|
||||
auto type = participant.vparticipant().type();
|
||||
if (type == mtpc_channelParticipantBanned) {
|
||||
auto &banned = participant.vparticipant().c_channelParticipantBanned();
|
||||
const auto participant = Api::ChatParticipant(
|
||||
result.data().vparticipant(),
|
||||
user);
|
||||
using Type = Api::ChatParticipant::Type;
|
||||
if (participant.type() == Type::Creator
|
||||
|| participant.type() == Type::Admin) {
|
||||
editRestrictions(true, {}, nullptr, 0);
|
||||
} else if (const auto since = participant.restrictedSince()) {
|
||||
editRestrictions(
|
||||
false,
|
||||
ChatRestrictionsInfo(banned.vbanned_rights()));
|
||||
} else {
|
||||
auto hasAdminRights = (type == mtpc_channelParticipantAdmin)
|
||||
|| (type == mtpc_channelParticipantCreator);
|
||||
editRestrictions(hasAdminRights, ChatRestrictionsInfo());
|
||||
participant.restrictions(),
|
||||
user->owner().user(participant.by()),
|
||||
since);
|
||||
}
|
||||
}).fail([=] {
|
||||
editRestrictions(false, ChatRestrictionsInfo());
|
||||
editRestrictions(false, {}, nullptr, 0);
|
||||
}).send();
|
||||
}
|
||||
}, &st::menuIconPermissions);
|
||||
|
||||
@@ -780,6 +780,7 @@ void GenerateItems(
|
||||
using LogChangeWallpaper = MTPDchannelAdminLogEventActionChangeWallpaper;
|
||||
using LogChangeEmojiStatus = MTPDchannelAdminLogEventActionChangeEmojiStatus;
|
||||
using LogToggleSignatureProfiles = MTPDchannelAdminLogEventActionToggleSignatureProfiles;
|
||||
using LogParticipantSubExtend = MTPDchannelAdminLogEventActionParticipantSubExtend;
|
||||
|
||||
const auto session = &history->session();
|
||||
const auto id = event.vid().v;
|
||||
@@ -2037,6 +2038,31 @@ void GenerateItems(
|
||||
addSimpleServiceMessage(text);
|
||||
};
|
||||
|
||||
const auto createParticipantSubExtend = [&](const LogParticipantSubExtend &action) {
|
||||
const auto participant = Api::ChatParticipant(
|
||||
action.vnew_participant(),
|
||||
channel);
|
||||
if (!participant.subscriptionDate()) {
|
||||
return;
|
||||
}
|
||||
const auto participantPeer = channel->owner().peer(participant.id());
|
||||
const auto participantPeerLink = participantPeer->createOpenLink();
|
||||
const auto participantPeerLinkText = Ui::Text::Link(
|
||||
participantPeer->name(),
|
||||
QString());
|
||||
const auto parsed = base::unixtime::parse(
|
||||
participant.subscriptionDate());
|
||||
addServiceMessageWithLink(
|
||||
tr::lng_admin_log_subscription_extend(
|
||||
tr::now,
|
||||
lt_name,
|
||||
participantPeerLinkText,
|
||||
lt_date,
|
||||
{ langDateTimeFull(parsed) },
|
||||
Ui::Text::WithEntities),
|
||||
participantPeerLink);
|
||||
};
|
||||
|
||||
action.match(
|
||||
createChangeTitle,
|
||||
createChangeAbout,
|
||||
@@ -2086,7 +2112,8 @@ void GenerateItems(
|
||||
createChangeProfilePeerColor,
|
||||
createChangeWallpaper,
|
||||
createChangeEmojiStatus,
|
||||
createToggleSignatureProfiles);
|
||||
createToggleSignatureProfiles,
|
||||
createParticipantSubExtend);
|
||||
}
|
||||
|
||||
} // namespace AdminLog
|
||||
|
||||
@@ -579,13 +579,16 @@ void HistoryInner::setupSharingDisallowed() {
|
||||
}
|
||||
|
||||
void HistoryInner::setupSwipeReply() {
|
||||
if (_peer && _peer->isChannel() && !_peer->isMegagroup()) {
|
||||
return;
|
||||
}
|
||||
HistoryView::SetupSwipeHandler(this, _scroll, [=, history = _history](
|
||||
HistoryView::ChatPaintGestureHorizontalData data) {
|
||||
const auto changed = (_gestureHorizontal.msgBareId != data.msgBareId)
|
||||
|| (_gestureHorizontal.translation != data.translation)
|
||||
|| (_gestureHorizontal.reachRatio != data.reachRatio);
|
||||
_gestureHorizontal = data;
|
||||
if (changed) {
|
||||
_gestureHorizontal = data;
|
||||
const auto item = history->peer->owner().message(
|
||||
history->peer->id,
|
||||
MsgId{ data.msgBareId });
|
||||
@@ -4272,32 +4275,32 @@ auto HistoryInner::findViewForPinnedTracking(int top) const
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
|
||||
void HistoryInner::refreshAboutView() {
|
||||
void HistoryInner::refreshAboutView(bool force) {
|
||||
const auto refresh = [&] {
|
||||
if (force) {
|
||||
_aboutView = nullptr;
|
||||
}
|
||||
if (!_aboutView) {
|
||||
_aboutView = std::make_unique<HistoryView::AboutView>(
|
||||
_history,
|
||||
_history->delegateMixin()->delegate());
|
||||
}
|
||||
};
|
||||
if (const auto user = _peer->asUser()) {
|
||||
if (const auto info = user->botInfo.get()) {
|
||||
if (!_aboutView) {
|
||||
_aboutView = std::make_unique<HistoryView::AboutView>(
|
||||
_history,
|
||||
_history->delegateMixin()->delegate());
|
||||
}
|
||||
refresh();
|
||||
if (!info->inited) {
|
||||
session().api().requestFullPeer(user);
|
||||
}
|
||||
} else if (user->meRequiresPremiumToWrite()
|
||||
&& !user->session().premium()
|
||||
&& !historyHeight()) {
|
||||
if (!_aboutView) {
|
||||
_aboutView = std::make_unique<HistoryView::AboutView>(
|
||||
_history,
|
||||
_history->delegateMixin()->delegate());
|
||||
}
|
||||
refresh();
|
||||
} else if (!historyHeight()) {
|
||||
if (!user->isFullLoaded()) {
|
||||
session().api().requestFullPeer(user);
|
||||
} else if (!_aboutView) {
|
||||
_aboutView = std::make_unique<HistoryView::AboutView>(
|
||||
_history,
|
||||
_history->delegateMixin()->delegate());
|
||||
} else {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ public:
|
||||
[[nodiscard]] std::pair<Element*, int> findViewForPinnedTracking(
|
||||
int top) const;
|
||||
|
||||
void refreshAboutView();
|
||||
void refreshAboutView(bool force = false);
|
||||
void notifyMigrateUpdated();
|
||||
|
||||
// Ui::AbstractTooltipShower interface.
|
||||
|
||||
@@ -2526,7 +2526,7 @@ bool HistoryItem::canReact() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void HistoryItem::addPaidReaction(int count, bool anonymous) {
|
||||
void HistoryItem::addPaidReaction(int count, std::optional<bool> anonymous) {
|
||||
Expects(count >= 0);
|
||||
Expects(_history->peer->isBroadcast() || isDiscussionPost());
|
||||
|
||||
@@ -2653,9 +2653,11 @@ auto HistoryItem::topPaidReactionsWithLocal() const
|
||||
const auto i = ranges::find_if(
|
||||
result,
|
||||
[](const TopPaid &entry) { return entry.my != 0; });
|
||||
const auto peer = _reactions->localPaidAnonymous()
|
||||
? nullptr
|
||||
: history()->session().user().get();
|
||||
const auto peerForMine = [&] {
|
||||
return _reactions->localPaidAnonymous()
|
||||
? nullptr
|
||||
: history()->session().user().get();
|
||||
};
|
||||
if (const auto local = _reactions->localPaidCount()) {
|
||||
const auto top = [&](int mine) {
|
||||
return ranges::count_if(result, [&](const TopPaid &entry) {
|
||||
@@ -2664,18 +2666,18 @@ auto HistoryItem::topPaidReactionsWithLocal() const
|
||||
};
|
||||
if (i != end(result)) {
|
||||
i->count += local;
|
||||
i->peer = peer;
|
||||
i->peer = peerForMine();
|
||||
i->top = top(i->count) ? 1 : 0;
|
||||
} else {
|
||||
result.push_back({
|
||||
.peer = peer,
|
||||
.peer = peerForMine(),
|
||||
.count = uint32(local),
|
||||
.top = uint32(top(local) ? 1 : 0),
|
||||
.my = uint32(1),
|
||||
});
|
||||
}
|
||||
} else if (i != end(result)) {
|
||||
i->peer = peer;
|
||||
i->peer = peerForMine();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -5147,15 +5149,30 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
};
|
||||
|
||||
auto prepareGiveawayLaunch = [&](const MTPDmessageActionGiveawayLaunch &action) {
|
||||
const auto credits = action.vstars().value_or_empty();
|
||||
auto result = PreparedServiceText();
|
||||
result.links.push_back(fromLink());
|
||||
result.text = (_history->peer->isMegagroup()
|
||||
? tr::lng_action_giveaway_started_group
|
||||
: tr::lng_action_giveaway_started)(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText(), // Link 1.
|
||||
Ui::Text::WithEntities);
|
||||
result.text = credits
|
||||
? (_history->peer->isMegagroup()
|
||||
? tr::lng_action_giveaway_credits_started_group
|
||||
: tr::lng_action_giveaway_credits_started)(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText(), // Link 1.
|
||||
lt_amount,
|
||||
tr::lng_action_giveaway_credits_started_amount(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
float64(credits),
|
||||
Ui::Text::Bold),
|
||||
Ui::Text::WithEntities)
|
||||
: (_history->peer->isMegagroup()
|
||||
? tr::lng_action_giveaway_started_group
|
||||
: tr::lng_action_giveaway_started)(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText(), // Link 1.
|
||||
Ui::Text::WithEntities);
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -5163,15 +5180,20 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
auto result = PreparedServiceText();
|
||||
const auto winners = action.vwinners_count().v;
|
||||
const auto unclaimed = action.vunclaimed_count().v;
|
||||
const auto credits = action.is_stars();
|
||||
result.text = {
|
||||
(!winners
|
||||
? tr::lng_action_giveaway_results_none(tr::now)
|
||||
: unclaimed
|
||||
: (credits && unclaimed)
|
||||
? tr::lng_action_giveaway_results_credits_some(tr::now)
|
||||
: (!credits && unclaimed)
|
||||
? tr::lng_action_giveaway_results_some(tr::now)
|
||||
: tr::lng_action_giveaway_results(
|
||||
: (credits && !unclaimed)
|
||||
? tr::lng_action_giveaway_results_credits(
|
||||
tr::now,
|
||||
lt_count,
|
||||
winners))
|
||||
winners)
|
||||
: tr::lng_action_giveaway_results(tr::now, lt_count, winners))
|
||||
};
|
||||
return result;
|
||||
};
|
||||
@@ -5232,6 +5254,22 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
return result;
|
||||
};
|
||||
|
||||
auto prepareGiftPrize = [&](
|
||||
const MTPDmessageActionPrizeStars &action) {
|
||||
auto result = PreparedServiceText();
|
||||
_history->session().giftBoxStickersPacks().load();
|
||||
result.text = {
|
||||
(action.is_unclaimed()
|
||||
? tr::lng_prize_unclaimed_about
|
||||
: tr::lng_prize_about)(
|
||||
tr::now,
|
||||
lt_channel,
|
||||
_from->owner().peer(
|
||||
peerFromMTP(action.vboost_peer()))->name()),
|
||||
};
|
||||
return result;
|
||||
};
|
||||
|
||||
setServiceText(action.match(
|
||||
prepareChatAddUserText,
|
||||
prepareChatJoinedByLink,
|
||||
@@ -5276,6 +5314,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
prepareBoostApply,
|
||||
preparePaymentRefunded,
|
||||
prepareGiftStars,
|
||||
prepareGiftPrize,
|
||||
PrepareEmptyText<MTPDmessageActionRequestedPeerSentMe>,
|
||||
PrepareErrorText<MTPDmessageActionEmpty>));
|
||||
|
||||
@@ -5372,8 +5411,22 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
|
||||
_media = std::make_unique<Data::MediaGiftBox>(
|
||||
this,
|
||||
_from,
|
||||
Data::GiftType::Stars,
|
||||
Data::GiftType::Credits,
|
||||
data.vstars().v);
|
||||
}, [&](const MTPDmessageActionPrizeStars &data) {
|
||||
_media = std::make_unique<Data::MediaGiftBox>(
|
||||
this,
|
||||
_from,
|
||||
Data::GiftCode{
|
||||
.slug = qs(data.vtransaction_id()),
|
||||
.channel = history()->owner().channel(
|
||||
peerToChannel(peerFromMTP(data.vboost_peer()))),
|
||||
.count = int(data.vstars().v),
|
||||
.giveawayMsgId = data.vgiveaway_msg_id().v,
|
||||
.type = Data::GiftType::Credits,
|
||||
.viaGiveaway = true,
|
||||
.unclaimed = data.is_unclaimed(),
|
||||
});
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
|
||||
@@ -449,7 +449,7 @@ public:
|
||||
void toggleReaction(
|
||||
const Data::ReactionId &reaction,
|
||||
HistoryReactionSource source);
|
||||
void addPaidReaction(int count, bool anonymous);
|
||||
void addPaidReaction(int count, std::optional<bool> anonymous = {});
|
||||
void cancelScheduledPaidReaction();
|
||||
[[nodiscard]] Data::PaidReactionSend startPaidReactionSending();
|
||||
void finishPaidReactionSending(
|
||||
|
||||
@@ -20,6 +20,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSwipeSlow = 0.2;
|
||||
|
||||
} // namespace
|
||||
|
||||
void SetupSwipeHandler(
|
||||
not_null<Ui::RpWidget*> widget,
|
||||
@@ -30,6 +35,12 @@ void SetupSwipeHandler(
|
||||
constexpr auto kThresholdWidth = 50;
|
||||
constexpr auto kMaxRatio = 1.5;
|
||||
const auto threshold = style::ConvertFloatScale(kThresholdWidth);
|
||||
struct UpdateArgs {
|
||||
QPoint globalCursor;
|
||||
QPointF position;
|
||||
QPointF delta;
|
||||
bool touch = false;
|
||||
};
|
||||
struct State {
|
||||
base::unique_qptr<QObject> filter;
|
||||
Ui::Animations::Simple animationReach;
|
||||
@@ -44,6 +55,8 @@ void SetupSwipeHandler(
|
||||
bool started = false;
|
||||
bool reached = false;
|
||||
bool touch = false;
|
||||
bool twoFingerScrollStarted = false;
|
||||
std::optional<UpdateArgs> pendingUpdate;
|
||||
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
@@ -55,10 +68,16 @@ void SetupSwipeHandler(
|
||||
}, state->lifetime);
|
||||
|
||||
const auto updateRatio = [=](float64 ratio) {
|
||||
state->data.ratio = std::clamp(ratio, 0., kMaxRatio),
|
||||
ratio = std::max(ratio, 0.);
|
||||
state->data.ratio = ratio;
|
||||
const auto overscrollRatio = std::max(ratio - 1., 0.);
|
||||
const auto translation = int(
|
||||
base::SafeRound(-std::min(ratio, 1.) * threshold)
|
||||
) + Ui::OverscrollFromAccumulated(int(
|
||||
base::SafeRound(-overscrollRatio * threshold)
|
||||
));
|
||||
state->data.msgBareId = state->finishByTopData.msgBareId;
|
||||
state->data.translation = int(
|
||||
base::SafeRound(-std::clamp(ratio, 0., kMaxRatio) * threshold));
|
||||
state->data.translation = translation;
|
||||
state->data.cursorTop = state->cursorTop;
|
||||
update(state->data);
|
||||
};
|
||||
@@ -101,13 +120,7 @@ void SetupSwipeHandler(
|
||||
state->data.reachRatio = value;
|
||||
update(state->data);
|
||||
};
|
||||
struct UpdateArgs {
|
||||
QPoint globalCursor;
|
||||
QPointF position;
|
||||
QPointF delta;
|
||||
bool touch = false;
|
||||
};
|
||||
const auto updateWith = [=](UpdateArgs &&args) {
|
||||
const auto updateWith = [=](UpdateArgs args) {
|
||||
if (!state->started || state->touch != args.touch) {
|
||||
state->started = true;
|
||||
state->touch = args.touch;
|
||||
@@ -191,9 +204,7 @@ void SetupSwipeHandler(
|
||||
};
|
||||
const auto cancel = released(0)
|
||||
|| released(1)
|
||||
|| (touchscreen
|
||||
? (touches.size() != 1)
|
||||
: (touches.size() <= 0 || touches.size() > 2))
|
||||
|| (touches.size() != (touchscreen ? 1 : 2))
|
||||
|| (type == QEvent::TouchEnd)
|
||||
|| (type == QEvent::TouchCancel);
|
||||
if (cancel) {
|
||||
@@ -201,14 +212,21 @@ void SetupSwipeHandler(
|
||||
? std::optional<QPointF>()
|
||||
: (state->startAt - touches[0].pos()));
|
||||
} else {
|
||||
updateWith({
|
||||
const auto args = UpdateArgs{
|
||||
.globalCursor = (touchscreen
|
||||
? touches[0].screenPos().toPoint()
|
||||
: QCursor::pos()),
|
||||
.position = touches[0].pos(),
|
||||
.delta = state->startAt - touches[0].pos(),
|
||||
.touch = true,
|
||||
});
|
||||
};
|
||||
#ifdef Q_OS_MAC
|
||||
if (!state->twoFingerScrollStarted) {
|
||||
state->pendingUpdate = args;
|
||||
return base::EventFilterResult::Cancel;
|
||||
}
|
||||
#endif // Q_OS_MAC
|
||||
updateWith(args);
|
||||
}
|
||||
return (touchscreen && state->orientation != Qt::Horizontal)
|
||||
? base::EventFilterResult::Continue
|
||||
@@ -217,6 +235,17 @@ void SetupSwipeHandler(
|
||||
case QEvent::Wheel: {
|
||||
const auto w = static_cast<QWheelEvent*>(e.get());
|
||||
const auto phase = w->phase();
|
||||
#ifdef Q_OS_MAC
|
||||
if (phase == Qt::ScrollBegin) {
|
||||
state->twoFingerScrollStarted = true;
|
||||
if (const auto update = base::take(state->pendingUpdate)) {
|
||||
updateWith((*update));
|
||||
}
|
||||
} else if (phase == Qt::ScrollEnd
|
||||
|| phase == Qt::ScrollMomentum) {
|
||||
state->twoFingerScrollStarted = false;
|
||||
}
|
||||
#endif // Q_OS_MAC
|
||||
if (Platform::IsMac() || phase == Qt::NoScrollPhase) {
|
||||
break;
|
||||
} else if (phase == Qt::ScrollBegin) {
|
||||
@@ -229,10 +258,12 @@ void SetupSwipeHandler(
|
||||
if (cancel) {
|
||||
processEnd();
|
||||
} else {
|
||||
const auto invert = (w->inverted() ? -1 : 1);
|
||||
const auto delta = Ui::ScrollDeltaF(w) * invert;
|
||||
updateWith({
|
||||
.globalCursor = w->globalPosition().toPoint(),
|
||||
.position = QPointF(),
|
||||
.delta = state->delta - Ui::ScrollDelta(w),
|
||||
.delta = state->delta + delta * kSwipeSlow,
|
||||
.touch = false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8017,13 +8017,18 @@ void HistoryWidget::handlePeerUpdate() {
|
||||
}
|
||||
}
|
||||
if (!_showAnimation) {
|
||||
if (_unblock->isHidden() == isBlocked()
|
||||
const auto blockChanged = (_unblock->isHidden() == isBlocked());
|
||||
if (blockChanged
|
||||
|| (!isBlocked() && _joinChannel->isHidden() == isJoinChannel())) {
|
||||
resize = true;
|
||||
}
|
||||
if (updateCanSendMessage()) {
|
||||
resize = true;
|
||||
}
|
||||
if (blockChanged) {
|
||||
_list->refreshAboutView(true);
|
||||
_list->updateBotInfo();
|
||||
}
|
||||
updateControlsVisibility();
|
||||
if (resize) {
|
||||
updateControlsGeometry();
|
||||
|
||||
@@ -245,6 +245,8 @@ bool AboutView::refresh() {
|
||||
} else if (user->meRequiresPremiumToWrite()
|
||||
&& !user->session().premium()) {
|
||||
setItem(makePremiumRequired(), nullptr);
|
||||
} else if (user->isBlocked()) {
|
||||
setItem(makeBlocked(), nullptr);
|
||||
} else {
|
||||
makeIntro(user);
|
||||
}
|
||||
@@ -393,4 +395,17 @@ AdminLog::OwnedItem AboutView::makePremiumRequired() {
|
||||
return result;
|
||||
}
|
||||
|
||||
AdminLog::OwnedItem AboutView::makeBlocked() {
|
||||
const auto item = _history->makeMessage({
|
||||
.id = _history->nextNonHistoryEntryId(),
|
||||
.flags = (MessageFlag::FakeAboutView
|
||||
| MessageFlag::FakeHistoryItem
|
||||
| MessageFlag::Local),
|
||||
.from = _history->peer->id,
|
||||
}, PreparedServiceText{
|
||||
{ tr::lng_chat_intro_default_title(tr::now) }
|
||||
});
|
||||
return AdminLog::OwnedItem(_delegate, item);
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
||||
|
||||
@@ -36,6 +36,7 @@ public:
|
||||
private:
|
||||
[[nodiscard]] AdminLog::OwnedItem makeAboutBot(not_null<BotInfo*> info);
|
||||
[[nodiscard]] AdminLog::OwnedItem makePremiumRequired();
|
||||
[[nodiscard]] AdminLog::OwnedItem makeBlocked();
|
||||
void makeIntro(not_null<UserData*> user);
|
||||
void setItem(AdminLog::OwnedItem item, DocumentData *sticker);
|
||||
void setHelloChosen(not_null<DocumentData*> sticker);
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_thread.h"
|
||||
#include "history/view/reactions/history_view_reactions_button.h"
|
||||
#include "history/view/history_view_corner_buttons.h"
|
||||
#include "history/view/history_view_list_widget.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
@@ -52,7 +53,8 @@ namespace {
|
||||
|
||||
class Item final
|
||||
: public Ui::Menu::ItemBase
|
||||
, private HistoryView::ListDelegate {
|
||||
, private ListDelegate
|
||||
, private CornerButtonsDelegate {
|
||||
public:
|
||||
Item(not_null<Ui::RpWidget*> parent, not_null<Data::Thread*> thread);
|
||||
|
||||
@@ -73,6 +75,7 @@ private:
|
||||
void setupHistory();
|
||||
void updateInnerVisibleArea();
|
||||
|
||||
// ListDelegate delegate.
|
||||
Context listContext() override;
|
||||
bool listScrollTo(int top, bool syntetic = true) override;
|
||||
void listCancelRequest() override;
|
||||
@@ -164,6 +167,16 @@ private:
|
||||
std::unique_ptr<QMimeData> data,
|
||||
Fn<void()> finished) override;
|
||||
|
||||
// CornerButtonsDelegate delegate.
|
||||
void cornerButtonsShowAtPosition(
|
||||
Data::MessagePosition position) override;
|
||||
Data::Thread *cornerButtonsThread() override;
|
||||
FullMsgId cornerButtonsCurrentId() override;
|
||||
bool cornerButtonsIgnoreVisibility() override;
|
||||
std::optional<bool> cornerButtonsDownShown() override;
|
||||
bool cornerButtonsUnreadMayBeShown() override;
|
||||
bool cornerButtonsHas(CornerButtonType type) override;
|
||||
|
||||
const not_null<QAction*> _dummyAction;
|
||||
const not_null<Main::Session*> _session;
|
||||
const not_null<Data::Thread*> _thread;
|
||||
@@ -176,7 +189,8 @@ private:
|
||||
const std::unique_ptr<Ui::ElasticScroll> _scroll;
|
||||
const std::unique_ptr<Ui::FlatButton> _markRead;
|
||||
|
||||
QPointer<HistoryView::ListWidget> _inner;
|
||||
QPointer<ListWidget> _inner;
|
||||
std::unique_ptr<CornerButtons> _cornerButtons;
|
||||
rpl::event_stream<ChatPreviewAction> _actions;
|
||||
|
||||
QImage _bg;
|
||||
@@ -446,11 +460,16 @@ void Item::setupHistory() {
|
||||
this,
|
||||
_session,
|
||||
static_cast<ListDelegate*>(this)));
|
||||
_cornerButtons = std::make_unique<CornerButtons>(
|
||||
_scroll.get(),
|
||||
_chatStyle.get(),
|
||||
static_cast<CornerButtonsDelegate*>(this));
|
||||
|
||||
_markRead->shownValue() | rpl::start_with_next([=](bool shown) {
|
||||
const auto top = _top->height();
|
||||
const auto bottom = shown ? _markRead->height() : 0;
|
||||
_scroll->setGeometry(rect().marginsRemoved({ 0, top, 0, bottom }));
|
||||
_cornerButtons->updatePositions();
|
||||
}, _markRead->lifetime());
|
||||
|
||||
_scroll->scrolls(
|
||||
@@ -495,6 +514,7 @@ void Item::paintEvent(QPaintEvent *e) {
|
||||
void Item::updateInnerVisibleArea() {
|
||||
const auto scrollTop = _scroll->scrollTop();
|
||||
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
|
||||
_cornerButtons->updateJumpDownVisibility();
|
||||
}
|
||||
|
||||
Context Item::listContext() {
|
||||
@@ -592,18 +612,28 @@ MessagesBarData Item::listMessagesBar(
|
||||
return {};
|
||||
}
|
||||
|
||||
auto skipped = false;
|
||||
const auto hidden = _replies && (repliesTill < 2);
|
||||
for (auto i = 0, count = int(elements.size()); i != count; ++i) {
|
||||
const auto item = elements[i]->data();
|
||||
if (!item->isRegular()
|
||||
|| item->out()
|
||||
|| (_replies && !item->replyToId())) {
|
||||
if (!item->isRegular() || (_replies && !item->replyToId())) {
|
||||
continue;
|
||||
}
|
||||
const auto inHistory = (item->history() == _history);
|
||||
if ((_replies && item->id > repliesTill)
|
||||
const auto unread = (_replies && item->id > repliesTill)
|
||||
|| (migratedTill && (inHistory || item->id > migratedTill))
|
||||
|| (historyTill && inHistory && item->id > historyTill)) {
|
||||
|| (historyTill && inHistory && item->id > historyTill);
|
||||
if (!unread) {
|
||||
skipped = true;
|
||||
}
|
||||
if (item->out()) {
|
||||
continue;
|
||||
}
|
||||
if (unread) {
|
||||
if (!skipped) {
|
||||
// Don't show jumping unread bar if scrolling up from bottom.
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
.bar = {
|
||||
.element = elements[i],
|
||||
@@ -800,6 +830,46 @@ void Item::listLaunchDrag(
|
||||
Fn<void()> finished) {
|
||||
}
|
||||
|
||||
void Item::cornerButtonsShowAtPosition(Data::MessagePosition position) {
|
||||
if (position == Data::UnreadMessagePosition) {
|
||||
position = Data::MaxMessagePosition;
|
||||
}
|
||||
_inner->showAtPosition(
|
||||
position,
|
||||
{},
|
||||
_cornerButtons->doneJumpFrom(position.fullId, {}, true));
|
||||
}
|
||||
|
||||
Data::Thread *Item::cornerButtonsThread() {
|
||||
return _thread;
|
||||
}
|
||||
|
||||
FullMsgId Item::cornerButtonsCurrentId() {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Item::cornerButtonsIgnoreVisibility() {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<bool> Item::cornerButtonsDownShown() {
|
||||
const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
|
||||
if (top < _scroll->scrollTopMax()) {
|
||||
return true;
|
||||
} else if (_inner->loadedAtBottomKnown()) {
|
||||
return !_inner->loadedAtBottom();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool Item::cornerButtonsUnreadMayBeShown() {
|
||||
return _inner->loadedAtBottomKnown();
|
||||
}
|
||||
|
||||
bool Item::cornerButtonsHas(CornerButtonType type) {
|
||||
return (type == CornerButtonType::Down);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ChatPreview MakeChatPreview(
|
||||
|
||||
@@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "history/view/history_view_corner_buttons.h"
|
||||
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/controls/jump_down_button.h"
|
||||
#include "ui/widgets/elastic_scroll.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "base/qt/qt_key_modifiers.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
@@ -33,17 +34,41 @@ CornerButtons::CornerButtons(
|
||||
not_null<Ui::ScrollArea*> parent,
|
||||
not_null<const Ui::ChatStyle*> st,
|
||||
not_null<CornerButtonsDelegate*> delegate)
|
||||
: _scroll(parent)
|
||||
: CornerButtons(
|
||||
parent,
|
||||
[=](QEvent *e) { return parent->viewportEvent(e); },
|
||||
st,
|
||||
delegate) {
|
||||
}
|
||||
|
||||
CornerButtons::CornerButtons(
|
||||
not_null<Ui::ElasticScroll*> parent,
|
||||
not_null<const Ui::ChatStyle*> st,
|
||||
not_null<CornerButtonsDelegate*> delegate)
|
||||
: CornerButtons(
|
||||
parent,
|
||||
[=](QEvent *e) { return parent->viewportEvent(e); },
|
||||
st,
|
||||
delegate) {
|
||||
}
|
||||
|
||||
CornerButtons::CornerButtons(
|
||||
not_null<QWidget*> parent,
|
||||
Fn<bool(QEvent*)> scrollViewportEvent,
|
||||
not_null<const Ui::ChatStyle*> st,
|
||||
not_null<CornerButtonsDelegate*> delegate)
|
||||
: _parent(parent)
|
||||
, _scrollViewportEvent(std::move(scrollViewportEvent))
|
||||
, _delegate(delegate)
|
||||
, _down(
|
||||
parent,
|
||||
st->value(parent->lifetime(), st::historyToDown))
|
||||
st->value(_stLifetime, st::historyToDown))
|
||||
, _mentions(
|
||||
parent,
|
||||
st->value(parent->lifetime(), st::historyUnreadMentions))
|
||||
st->value(_stLifetime, st::historyUnreadMentions))
|
||||
, _reactions(
|
||||
parent,
|
||||
st->value(parent->lifetime(), st::historyUnreadReactions)) {
|
||||
st->value(_stLifetime, st::historyUnreadReactions)) {
|
||||
_down.widget->addClickHandler([=] { downClick(); });
|
||||
_mentions.widget->addClickHandler([=] { mentionsClick(); });
|
||||
_reactions.widget->addClickHandler([=] { reactionsClick(); });
|
||||
@@ -68,7 +93,7 @@ bool CornerButtons::eventFilter(QObject *o, QEvent *e) {
|
||||
&& (o == _down.widget
|
||||
|| o == _mentions.widget
|
||||
|| o == _reactions.widget)) {
|
||||
return _scroll->viewportEvent(e);
|
||||
return _scrollViewportEvent(e);
|
||||
}
|
||||
return QObject::eventFilter(o, e);
|
||||
}
|
||||
@@ -200,9 +225,7 @@ void CornerButtons::showAt(MsgId id) {
|
||||
}
|
||||
}
|
||||
|
||||
void CornerButtons::updateVisibility(
|
||||
CornerButtonType type,
|
||||
bool shown) {
|
||||
void CornerButtons::updateVisibility(Type type, bool shown) {
|
||||
auto &button = buttonByType(type);
|
||||
if (button.shown != shown) {
|
||||
button.shown = shown;
|
||||
@@ -291,7 +314,7 @@ void CornerButtons::updatePositions() {
|
||||
historyDownShown);
|
||||
_down.widget->moveToRight(
|
||||
st::historyToDownPosition.x(),
|
||||
_scroll->height() - top);
|
||||
_parent->height() - top);
|
||||
}
|
||||
{
|
||||
const auto right = anim::interpolate(
|
||||
@@ -302,7 +325,7 @@ void CornerButtons::updatePositions() {
|
||||
0,
|
||||
_down.widget->height() + skip,
|
||||
historyDownShown);
|
||||
const auto top = _scroll->height()
|
||||
const auto top = _parent->height()
|
||||
- _mentions.widget->height()
|
||||
- st::historyToDownPosition.y()
|
||||
- shift;
|
||||
@@ -321,7 +344,7 @@ void CornerButtons::updatePositions() {
|
||||
0,
|
||||
_mentions.widget->height() + skip,
|
||||
unreadMentionsShown);
|
||||
const auto top = _scroll->height()
|
||||
const auto top = _parent->height()
|
||||
- _reactions.widget->height()
|
||||
- st::historyToDownPosition.y()
|
||||
- shift;
|
||||
@@ -355,7 +378,7 @@ Fn<void(bool found)> CornerButtons::doneJumpFrom(
|
||||
}
|
||||
if (!found && !ignoreMessageNotFound) {
|
||||
Ui::Toast::Show(
|
||||
_scroll.get(),
|
||||
_parent.get(),
|
||||
tr::lng_message_not_found(tr::now));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ struct FullMsgId;
|
||||
namespace Ui {
|
||||
class ChatStyle;
|
||||
class ScrollArea;
|
||||
class ElasticScroll;
|
||||
class JumpDownButton;
|
||||
} // namespace Ui
|
||||
|
||||
@@ -61,6 +62,10 @@ public:
|
||||
not_null<Ui::ScrollArea*> parent,
|
||||
not_null<const Ui::ChatStyle*> st,
|
||||
not_null<CornerButtonsDelegate*> delegate);
|
||||
CornerButtons(
|
||||
not_null<Ui::ElasticScroll*> parent,
|
||||
not_null<const Ui::ChatStyle*> st,
|
||||
not_null<CornerButtonsDelegate*> delegate);
|
||||
|
||||
using Type = CornerButtonType;
|
||||
|
||||
@@ -91,6 +96,12 @@ public:
|
||||
bool ignoreMessageNotFound = false);
|
||||
|
||||
private:
|
||||
CornerButtons(
|
||||
not_null<QWidget*> parent,
|
||||
Fn<bool(QEvent*)> scrollViewportEvent,
|
||||
not_null<const Ui::ChatStyle*> st,
|
||||
not_null<CornerButtonsDelegate*> delegate);
|
||||
|
||||
bool eventFilter(QObject *o, QEvent *e) override;
|
||||
|
||||
void computeCurrentReplyReturn();
|
||||
@@ -99,9 +110,12 @@ private:
|
||||
[[nodiscard]] History *lookupHistory() const;
|
||||
void showAt(MsgId id);
|
||||
|
||||
const not_null<Ui::ScrollArea*> _scroll;
|
||||
const not_null<QWidget*> _parent;
|
||||
const Fn<bool(QEvent*)> _scrollViewportEvent;
|
||||
const not_null<CornerButtonsDelegate*> _delegate;
|
||||
|
||||
rpl::lifetime _stLifetime;
|
||||
|
||||
CornerButton _down;
|
||||
CornerButton _mentions;
|
||||
CornerButton _reactions;
|
||||
|
||||
@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/rect.h"
|
||||
#include "ui/round_rect.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/text/text_extended_data.h"
|
||||
#include "ui/power_saving.h"
|
||||
#include "data/components/factchecks.h"
|
||||
#include "data/components/sponsored_messages.h"
|
||||
@@ -1142,6 +1143,15 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||
const auto displayInfo = needInfoDisplay();
|
||||
const auto reactionsInBubble = _reactions && embedReactionsInBubble();
|
||||
|
||||
const auto keyboard = item->inlineReplyKeyboard();
|
||||
const auto fullGeometry = g;
|
||||
if (keyboard) {
|
||||
// We need to count geometry without keyboard for bubble selection
|
||||
// intervals counting below.
|
||||
const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
|
||||
g.setHeight(g.height() - keyboardHeight);
|
||||
}
|
||||
|
||||
auto mediaSelectionIntervals = (!context.selected() && mediaDisplayed)
|
||||
? media->getBubbleSelectionIntervals(context.selection)
|
||||
: std::vector<Ui::BubbleSelectionInterval>();
|
||||
@@ -1176,25 +1186,22 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||
if (customHighlight) {
|
||||
media->drawHighlight(p, context, localMediaTop);
|
||||
} else {
|
||||
paintHighlight(p, context, g.height());
|
||||
paintHighlight(p, context, fullGeometry.height());
|
||||
}
|
||||
|
||||
const auto roll = media ? media->bubbleRoll() : Media::BubbleRoll();
|
||||
if (roll) {
|
||||
p.save();
|
||||
p.translate(g.center());
|
||||
p.translate(fullGeometry.center());
|
||||
p.rotate(roll.rotate);
|
||||
p.scale(roll.scale, roll.scale);
|
||||
p.translate(-g.center());
|
||||
p.translate(-fullGeometry.center());
|
||||
}
|
||||
|
||||
p.setTextPalette(stm->textPalette);
|
||||
|
||||
const auto keyboard = item->inlineReplyKeyboard();
|
||||
const auto messageRounding = countMessageRounding();
|
||||
if (keyboard) {
|
||||
const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
|
||||
g.setHeight(g.height() - keyboardHeight);
|
||||
const auto keyboardPosition = QPoint(g.left(), g.top() + g.height() + st::msgBotKbButton.margin);
|
||||
p.translate(keyboardPosition);
|
||||
keyboard->paint(
|
||||
@@ -1506,10 +1513,12 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||
+ ((g.height() < size * kMaxHeightRatio)
|
||||
? rightActionSize().value_or(QSize()).width()
|
||||
: 0);
|
||||
const auto shift = std::min(
|
||||
(size * kShiftRatio * context.gestureHorizontal.ratio),
|
||||
-1. * context.gestureHorizontal.translation
|
||||
) + (st::historySwipeIconSkip * ratio * (isLeftSize ? .7 : 1.));
|
||||
const auto rect = QRectF(
|
||||
outerWidth
|
||||
- (size * kShiftRatio * context.gestureHorizontal.ratio)
|
||||
- (st::historySwipeIconSkip * ratio * (isLeftSize ? .7 : 1.)),
|
||||
outerWidth - shift,
|
||||
g.y() + (g.height() - size) / 2,
|
||||
size,
|
||||
size);
|
||||
@@ -3369,7 +3378,7 @@ void Message::refreshReactions() {
|
||||
item,
|
||||
weak.get(),
|
||||
1,
|
||||
Payments::LookupMyPaidAnonymous(item),
|
||||
std::nullopt,
|
||||
controller->uiShow());
|
||||
return;
|
||||
} else {
|
||||
@@ -3568,6 +3577,9 @@ bool Message::allowTextSelectionByHandler(
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (dynamic_cast<Ui::Text::BlockquoteClickHandler*>(handler.get())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -885,8 +885,8 @@ void RepliesWidget::setupSwipeReply() {
|
||||
const auto changed = (_gestureHorizontal.msgBareId != data.msgBareId)
|
||||
|| (_gestureHorizontal.translation != data.translation)
|
||||
|| (_gestureHorizontal.reachRatio != data.reachRatio);
|
||||
_gestureHorizontal = data;
|
||||
if (changed) {
|
||||
_gestureHorizontal = data;
|
||||
const auto item = _history->peer->owner().message(
|
||||
_history->peer->id,
|
||||
MsgId{ data.msgBareId });
|
||||
|
||||
@@ -21,11 +21,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history_item_helpers.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/effects/credits_graphics.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
constexpr auto kOutlineRatio = 0.85;
|
||||
|
||||
auto GenerateGiveawayStart(
|
||||
not_null<Element*> parent,
|
||||
not_null<Data::GiveawayStart*> data)
|
||||
@@ -49,10 +52,20 @@ auto GenerateGiveawayStart(
|
||||
nullptr,
|
||||
sticker,
|
||||
st::chatGiveawayStickerPadding,
|
||||
tr::lng_prizes_badge(
|
||||
tr::now,
|
||||
lt_amount,
|
||||
QString::number(quantity))));
|
||||
data->credits
|
||||
? QString::number(data->credits)
|
||||
: tr::lng_prizes_badge(
|
||||
tr::now,
|
||||
lt_amount,
|
||||
QString::number(quantity)),
|
||||
data->credits
|
||||
? Ui::CreditsWhiteDoubledIcon(
|
||||
st::chatGiveawayCreditsIconHeight,
|
||||
kOutlineRatio)
|
||||
: QImage(),
|
||||
data->credits
|
||||
? std::make_optional(st::creditsBg3->c)
|
||||
: std::nullopt));
|
||||
|
||||
auto pushText = [&](
|
||||
TextWithEntities text,
|
||||
@@ -83,8 +96,29 @@ auto GenerateGiveawayStart(
|
||||
st::chatGiveawayPrizesWithPadding));
|
||||
}
|
||||
|
||||
pushText(
|
||||
tr::lng_prizes_about(
|
||||
pushText((data->credits && (quantity == 1))
|
||||
? tr::lng_prizes_credits_about_single(
|
||||
tr::now,
|
||||
lt_amount,
|
||||
tr::lng_prizes_credits_about_amount(
|
||||
tr::now,
|
||||
lt_count,
|
||||
data->credits,
|
||||
Ui::Text::RichLangValue),
|
||||
Ui::Text::RichLangValue)
|
||||
: (data->credits && (quantity > 1))
|
||||
? tr::lng_prizes_credits_about(
|
||||
tr::now,
|
||||
lt_count,
|
||||
quantity,
|
||||
lt_amount,
|
||||
tr::lng_prizes_credits_about_amount(
|
||||
tr::now,
|
||||
lt_count,
|
||||
data->credits,
|
||||
Ui::Text::RichLangValue),
|
||||
Ui::Text::RichLangValue)
|
||||
: tr::lng_prizes_about(
|
||||
tr::now,
|
||||
lt_count,
|
||||
quantity,
|
||||
@@ -186,10 +220,20 @@ auto GenerateGiveawayResults(
|
||||
nullptr,
|
||||
sticker,
|
||||
st::chatGiveawayStickerPadding,
|
||||
tr::lng_prizes_badge(
|
||||
tr::now,
|
||||
lt_amount,
|
||||
QString::number(quantity))));
|
||||
data->credits
|
||||
? QString::number(data->credits)
|
||||
: tr::lng_prizes_badge(
|
||||
tr::now,
|
||||
lt_amount,
|
||||
QString::number(quantity)),
|
||||
data->credits
|
||||
? Ui::CreditsWhiteDoubledIcon(
|
||||
st::chatGiveawayCreditsIconHeight,
|
||||
kOutlineRatio)
|
||||
: QImage(),
|
||||
data->credits
|
||||
? std::make_optional(st::creditsBg3->c)
|
||||
: std::nullopt));
|
||||
|
||||
auto pushText = [&](
|
||||
TextWithEntities text,
|
||||
@@ -237,7 +281,17 @@ auto GenerateGiveawayResults(
|
||||
data->winnersCount - data->winners.size())),
|
||||
st::chatGiveawayNoCountriesTitleMargin);
|
||||
}
|
||||
pushText({ data->unclaimedCount
|
||||
pushText({ (data->credits && isSingleWinner)
|
||||
? tr::lng_prizes_credits_results_one(
|
||||
tr::now,
|
||||
lt_count,
|
||||
data->credits)
|
||||
: (data->credits && !isSingleWinner)
|
||||
? tr::lng_prizes_credits_results_all(
|
||||
tr::now,
|
||||
lt_count,
|
||||
data->credits)
|
||||
: data->unclaimedCount
|
||||
? tr::lng_prizes_results_some(tr::now)
|
||||
: isSingleWinner
|
||||
? tr::lng_prizes_results_one(tr::now)
|
||||
|
||||
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/dynamic_image.h"
|
||||
#include "ui/dynamic_thumbnails.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/round_rect.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
@@ -438,9 +439,13 @@ StickerWithBadgePart::StickerWithBadgePart(
|
||||
Element *replacing,
|
||||
Fn<Data()> lookup,
|
||||
QMargins padding,
|
||||
QString badge)
|
||||
: _sticker(parent, replacing, std::move(lookup), padding)
|
||||
, _badgeText(badge) {
|
||||
QString badge,
|
||||
QImage customLeftIcon,
|
||||
std::optional<QColor> colorOverride)
|
||||
: _customLeftIcon(std::move(customLeftIcon))
|
||||
, _sticker(parent, replacing, std::move(lookup), padding)
|
||||
, _badgeText(badge)
|
||||
, _colorOverride(std::move(colorOverride)) {
|
||||
}
|
||||
|
||||
void StickerWithBadgePart::draw(
|
||||
@@ -500,12 +505,19 @@ void StickerWithBadgePart::paintBadge(
|
||||
{
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(context.messageStyle()->msgFileBg);
|
||||
if (_colorOverride) {
|
||||
p.setBrush(*_colorOverride);
|
||||
} else {
|
||||
p.setBrush(context.messageStyle()->msgFileBg);
|
||||
}
|
||||
const auto half = st::chatGiveawayBadgeStroke / 2.;
|
||||
const auto inner = QRectF(rect).marginsRemoved(
|
||||
{ half, half, half, half });
|
||||
const auto inner = QRectF(rect) - Margins(half);
|
||||
const auto radius = inner.height() / 2.;
|
||||
p.drawRoundedRect(inner, radius, radius);
|
||||
if (_colorOverride && context.selected()) {
|
||||
p.setBrush(context.st->msgStickerOverlay());
|
||||
p.drawRoundedRect(inner, radius, radius);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_sticker.parent()->usesBubblePattern(context)) {
|
||||
@@ -534,9 +546,12 @@ void StickerWithBadgePart::validateBadge(
|
||||
const auto &font = st::chatGiveawayBadgeFont;
|
||||
_badgeFg = badgeFg;
|
||||
_badgeBorder = badgeBorder;
|
||||
const auto width = font->width(_badgeText);
|
||||
const auto iconWidth = _customLeftIcon.isNull()
|
||||
? 0
|
||||
: (_customLeftIcon.width() / style::DevicePixelRatio());
|
||||
const auto width = font->width(_badgeText) + iconWidth;
|
||||
const auto inner = QRect(0, 0, width, font->height);
|
||||
const auto rect = inner.marginsAdded(st::chatGiveawayBadgePadding);
|
||||
const auto rect = inner + st::chatGiveawayBadgePadding;
|
||||
const auto size = rect.size();
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
_badge = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
|
||||
@@ -548,17 +563,29 @@ void StickerWithBadgePart::validateBadge(
|
||||
p.setPen(QPen(_badgeBorder, st::chatGiveawayBadgeStroke * 1.));
|
||||
p.setBrush(Qt::NoBrush);
|
||||
const auto half = st::chatGiveawayBadgeStroke / 2.;
|
||||
const auto smaller = QRectF(
|
||||
rect.translated(-rect.topLeft())
|
||||
).marginsRemoved({ half, half, half, half });
|
||||
const auto left = _customLeftIcon.isNull()
|
||||
? st::chatGiveawayBadgePadding.left()
|
||||
: (st::chatGiveawayBadgePadding.left() - half * 2);
|
||||
const auto smaller = QRectF(rect.translated(-rect.topLeft()))
|
||||
- Margins(half);
|
||||
const auto radius = smaller.height() / 2.;
|
||||
p.drawRoundedRect(smaller, radius, radius);
|
||||
p.setPen(_badgeFg);
|
||||
p.setFont(font);
|
||||
p.drawText(
|
||||
st::chatGiveawayBadgePadding.left(),
|
||||
left + iconWidth,
|
||||
st::chatGiveawayBadgePadding.top() + font->ascent,
|
||||
_badgeText);
|
||||
if (!_customLeftIcon.isNull()) {
|
||||
const auto iconHeight = _customLeftIcon.height()
|
||||
/ style::DevicePixelRatio();
|
||||
p.drawImage(
|
||||
left,
|
||||
half + (inner.height() - iconHeight) / 2,
|
||||
context.selected()
|
||||
? Images::Colored(base::duplicate(_customLeftIcon), _badgeFg)
|
||||
: _customLeftIcon);
|
||||
}
|
||||
}
|
||||
|
||||
PeerBubbleListPart::PeerBubbleListPart(
|
||||
|
||||
@@ -227,7 +227,9 @@ public:
|
||||
Element *replacing,
|
||||
Fn<Data()> lookup,
|
||||
QMargins padding,
|
||||
QString badge);
|
||||
QString badge,
|
||||
QImage customLeftIcon,
|
||||
std::optional<QColor> colorOverride);
|
||||
|
||||
void draw(
|
||||
Painter &p,
|
||||
@@ -252,12 +254,14 @@ private:
|
||||
void validateBadge(const PaintContext &context) const;
|
||||
void paintBadge(Painter &p, const PaintContext &context) const;
|
||||
|
||||
const QImage _customLeftIcon;
|
||||
StickerInBubblePart _sticker;
|
||||
QString _badgeText;
|
||||
mutable QColor _badgeFg;
|
||||
mutable QColor _badgeBorder;
|
||||
mutable QImage _badge;
|
||||
mutable QImage _badgeCache;
|
||||
std::optional<QColor> _colorOverride;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "history/view/media/history_view_premium_gift.h"
|
||||
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/gift_premium_box.h" // ResolveGiftCode
|
||||
#include "chat_helpers/stickers_gift_box_pack.h"
|
||||
#include "core/click_handler_types.h" // ClickHandlerContext
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
@@ -19,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_credits.h" // Settings::CreditsId
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "settings/settings_credits_graphics.h" // GiftedCreditsBox
|
||||
#include "settings/settings_premium.h" // Settings::ShowGiftPremium
|
||||
#include "ui/layers/generic_box.h"
|
||||
@@ -49,7 +52,9 @@ QSize PremiumGift::size() {
|
||||
}
|
||||
|
||||
QString PremiumGift::title() {
|
||||
if (const auto count = stars()) {
|
||||
if (creditsPrize()) {
|
||||
return tr::lng_prize_title(tr::now);
|
||||
} else if (const auto count = credits()) {
|
||||
return tr::lng_gift_stars_title(tr::now, lt_count, count);
|
||||
}
|
||||
return gift()
|
||||
@@ -60,7 +65,8 @@ QString PremiumGift::title() {
|
||||
}
|
||||
|
||||
TextWithEntities PremiumGift::subtitle() {
|
||||
if (const auto count = stars()) {
|
||||
const auto isCreditsPrize = creditsPrize();
|
||||
if (const auto count = credits(); count && !isCreditsPrize) {
|
||||
return outgoingGift()
|
||||
? tr::lng_gift_stars_outgoing(
|
||||
tr::now,
|
||||
@@ -82,20 +88,32 @@ TextWithEntities PremiumGift::subtitle() {
|
||||
Ui::Text::Bold(name),
|
||||
Ui::Text::RichLangValue);
|
||||
result.append("\n\n");
|
||||
result.append((_data.unclaimed
|
||||
? tr::lng_prize_unclaimed_duration
|
||||
: _data.viaGiveaway
|
||||
? tr::lng_prize_duration
|
||||
: tr::lng_prize_gift_duration)(
|
||||
result.append(isCreditsPrize
|
||||
? tr::lng_prize_credits(
|
||||
tr::now,
|
||||
lt_duration,
|
||||
Ui::Text::Bold(GiftDuration(_data.count)),
|
||||
Ui::Text::RichLangValue));
|
||||
lt_amount,
|
||||
tr::lng_prize_credits_amount(
|
||||
tr::now,
|
||||
lt_count,
|
||||
credits(),
|
||||
Ui::Text::RichLangValue),
|
||||
Ui::Text::RichLangValue)
|
||||
: (_data.unclaimed
|
||||
? tr::lng_prize_unclaimed_duration
|
||||
: _data.viaGiveaway
|
||||
? tr::lng_prize_duration
|
||||
: tr::lng_prize_gift_duration)(
|
||||
tr::now,
|
||||
lt_duration,
|
||||
Ui::Text::Bold(GiftDuration(_data.count)),
|
||||
Ui::Text::RichLangValue));
|
||||
return result;
|
||||
}
|
||||
|
||||
rpl::producer<QString> PremiumGift::button() {
|
||||
return (gift() && (outgoingGift() || !_data.unclaimed))
|
||||
return creditsPrize()
|
||||
? tr::lng_view_button_giftcode()
|
||||
: (gift() && (outgoingGift() || !_data.unclaimed))
|
||||
? tr::lng_sticker_premium_view()
|
||||
: tr::lng_prize_open();
|
||||
}
|
||||
@@ -110,7 +128,26 @@ ClickHandlerPtr PremiumGift::createViewLink() {
|
||||
if (const auto controller = my.sessionWindow.get()) {
|
||||
const auto selfId = controller->session().userPeerId();
|
||||
const auto sent = (from->id == selfId);
|
||||
if (data.type == Data::GiftType::Stars) {
|
||||
if (creditsPrize()) {
|
||||
using Type = Data::CreditsHistoryEntry::PeerType;
|
||||
controller->show(Box(
|
||||
Settings::ReceiptCreditsBox,
|
||||
controller,
|
||||
Data::CreditsHistoryEntry{
|
||||
.id = data.slug,
|
||||
.title = QString(),
|
||||
.description = QString(),
|
||||
.date = base::unixtime::parse(date),
|
||||
.credits = uint64(data.count),
|
||||
.barePeerId = data.channel
|
||||
? data.channel->id.value
|
||||
: 0,
|
||||
.bareGiveawayMsgId = uint64(data.giveawayMsgId),
|
||||
.peerType = Type::Peer,
|
||||
.in = true,
|
||||
},
|
||||
Data::SubscriptionEntry()));
|
||||
} else if (data.type == Data::GiftType::Credits) {
|
||||
const auto to = sent ? peer : peer->session().user();
|
||||
controller->show(Box(
|
||||
Settings::GiftedCreditsBox,
|
||||
@@ -186,8 +223,14 @@ bool PremiumGift::gift() const {
|
||||
return _data.slug.isEmpty() || !_data.channel;
|
||||
}
|
||||
|
||||
int PremiumGift::stars() const {
|
||||
return (_data.type == Data::GiftType::Stars) ? _data.count : 0;
|
||||
bool PremiumGift::creditsPrize() const {
|
||||
return _data.viaGiveaway
|
||||
&& (_data.type == Data::GiftType::Credits)
|
||||
&& !_data.slug.isEmpty();
|
||||
}
|
||||
|
||||
int PremiumGift::credits() const {
|
||||
return (_data.type == Data::GiftType::Credits) ? _data.count : 0;
|
||||
}
|
||||
|
||||
void PremiumGift::ensureStickerCreated() const {
|
||||
@@ -196,7 +239,7 @@ void PremiumGift::ensureStickerCreated() const {
|
||||
}
|
||||
const auto &session = _parent->history()->session();
|
||||
auto &packs = session.giftBoxStickersPacks();
|
||||
const auto count = stars();
|
||||
const auto count = credits();
|
||||
const auto months = count ? packs.monthsForStars(count) : _data.count;
|
||||
if (const auto document = packs.lookup(months)) {
|
||||
if (const auto sticker = document->sticker()) {
|
||||
|
||||
@@ -49,7 +49,8 @@ private:
|
||||
[[nodiscard]] bool incomingGift() const;
|
||||
[[nodiscard]] bool outgoingGift() const;
|
||||
[[nodiscard]] bool gift() const;
|
||||
[[nodiscard]] int stars() const;
|
||||
[[nodiscard]] bool creditsPrize() const;
|
||||
[[nodiscard]] int credits() const;
|
||||
void ensureStickerCreated() const;
|
||||
|
||||
const not_null<Element*> _parent;
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "info/channel_statistics/boosts/create_giveaway_box.h"
|
||||
|
||||
#include "api/api_credits.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "base/unixtime.h"
|
||||
@@ -19,38 +20,46 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/channel_statistics/boosts/info_boosts_widget.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "info/statistics/info_statistics_list_controllers.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "payments/payments_checkout_process.h" // Payments::CheckoutProcess
|
||||
#include "payments/payments_form.h" // Payments::InvoicePremiumGiftCode
|
||||
#include "settings/settings_common.h"
|
||||
#include "settings/settings_premium.h" // Settings::ShowPremium
|
||||
#include "ui/boxes/choose_date_time.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/effects/credits_graphics.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/effects/premium_top_bar.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/continuous_sliders.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_premium.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_statistics.h"
|
||||
|
||||
#include <xxhash.h> // XXH64.
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kDoneTooltipDuration = 5 * crl::time(1000);
|
||||
constexpr auto kAdditionalPrizeLengthMax = 128;
|
||||
constexpr auto kColorIndexCredits = int(1);
|
||||
|
||||
[[nodiscard]] QDateTime ThreeDaysAfterToday() {
|
||||
auto dateNow = QDateTime::currentDateTime();
|
||||
@@ -63,6 +72,19 @@ constexpr auto kAdditionalPrizeLengthMax = 128;
|
||||
return dateNow;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64 UniqueIdFromCreditsOption(
|
||||
const Data::CreditsGiveawayOption &d,
|
||||
not_null<PeerData*> peer) {
|
||||
const auto string = QString::number(d.credits)
|
||||
+ d.storeProduct
|
||||
+ d.currency
|
||||
+ QString::number(d.amount)
|
||||
+ QString::number(peer->id.value)
|
||||
+ QString::number(peer->session().uniqueId());
|
||||
|
||||
return XXH64(string.data(), string.size() * sizeof(ushort), 0);
|
||||
}
|
||||
|
||||
[[nodiscard]] Fn<bool(int)> CreateErrorCallback(
|
||||
int max,
|
||||
tr::phrase<lngtag_count> phrase) {
|
||||
@@ -91,7 +113,7 @@ void AddPremiumTopBarWithDefaultTitleBar(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
rpl::producer<> showFinished,
|
||||
rpl::producer<QString> titleText,
|
||||
bool group) {
|
||||
rpl::producer<TextWithEntities> subtitleText) {
|
||||
struct State final {
|
||||
Ui::Animations::Simple animation;
|
||||
Ui::Text::String title;
|
||||
@@ -178,9 +200,7 @@ void AddPremiumTopBarWithDefaultTitleBar(
|
||||
Ui::Premium::TopBarDescriptor{
|
||||
.clickContextOther = nullptr,
|
||||
.title = tr::lng_giveaway_new_title(),
|
||||
.about = (group
|
||||
? tr::lng_giveaway_new_about_group
|
||||
: tr::lng_giveaway_new_about)(Ui::Text::RichLangValue),
|
||||
.about = std::move(subtitleText),
|
||||
.light = true,
|
||||
.optimizeMinistars = false,
|
||||
});
|
||||
@@ -248,11 +268,13 @@ void CreateGiveawayBox(
|
||||
|
||||
using GiveawayType = Giveaway::GiveawayTypeRow::Type;
|
||||
using GiveawayGroup = Ui::RadioenumGroup<GiveawayType>;
|
||||
using CreditsGroup = Ui::RadioenumGroup<int>;
|
||||
struct State final {
|
||||
State(not_null<PeerData*> p) : apiOptions(p) {
|
||||
State(not_null<PeerData*> p) : apiOptions(p), apiCreditsOptions(p) {
|
||||
}
|
||||
|
||||
Api::PremiumGiftCodeOptions apiOptions;
|
||||
Api::CreditsGiveawayOptions apiCreditsOptions;
|
||||
rpl::lifetime lifetimeApi;
|
||||
|
||||
std::vector<not_null<PeerData*>> selectedToAward;
|
||||
@@ -274,17 +296,43 @@ void CreateGiveawayBox(
|
||||
const auto group = peer->isMegagroup();
|
||||
const auto state = box->lifetime().make_state<State>(peer);
|
||||
const auto typeGroup = std::make_shared<GiveawayGroup>();
|
||||
const auto creditsGroup = std::make_shared<CreditsGroup>();
|
||||
|
||||
const auto isPrepaidCredits = (prepaid && prepaid->credits);
|
||||
|
||||
const auto isSpecificUsers = [=] {
|
||||
return !state->selectedToAward.empty();
|
||||
};
|
||||
const auto hideSpecificUsersOn = [=] {
|
||||
return rpl::combine(
|
||||
state->typeValue.value(),
|
||||
state->toAwardAmountChanged.events_starting_with(
|
||||
rpl::empty_value()) | rpl::type_erased()
|
||||
) | rpl::map([=](GiveawayType type, auto) {
|
||||
return (type == GiveawayType::Credits) || !isSpecificUsers();
|
||||
});
|
||||
};
|
||||
|
||||
auto showFinished = Ui::BoxShowFinishes(box);
|
||||
AddPremiumTopBarWithDefaultTitleBar(
|
||||
box,
|
||||
rpl::duplicate(showFinished),
|
||||
rpl::conditional(
|
||||
state->typeValue.value(
|
||||
) | rpl::map(rpl::mappers::_1 == GiveawayType::Random),
|
||||
hideSpecificUsersOn(),
|
||||
tr::lng_giveaway_start(),
|
||||
tr::lng_giveaway_award()),
|
||||
peer->isMegagroup());
|
||||
rpl::conditional(
|
||||
isPrepaidCredits
|
||||
? rpl::single(true) | rpl::type_erased()
|
||||
: state->typeValue.value() | rpl::map(
|
||||
rpl::mappers::_1 == GiveawayType::Credits),
|
||||
(peer->isMegagroup()
|
||||
? tr::lng_giveaway_credits_new_about_group()
|
||||
: tr::lng_giveaway_credits_new_about()),
|
||||
(peer->isMegagroup()
|
||||
? tr::lng_giveaway_new_about_group()
|
||||
: tr::lng_giveaway_new_about())
|
||||
) | rpl::map(Ui::Text::RichLangValue));
|
||||
{
|
||||
const auto &padding = st::giveawayGiftCodeCoverDividerPadding;
|
||||
Ui::AddSkip(box->verticalLayout(), padding.bottom());
|
||||
@@ -319,16 +367,26 @@ void CreateGiveawayBox(
|
||||
contentWrap->entity()->add(
|
||||
object_ptr<Giveaway::GiveawayTypeRow>(
|
||||
box,
|
||||
GiveawayType::Prepaid,
|
||||
prepaid->id,
|
||||
prepaid->credits
|
||||
? GiveawayType::PrepaidCredits
|
||||
: GiveawayType::Prepaid,
|
||||
prepaid->credits ? kColorIndexCredits : prepaid->id,
|
||||
tr::lng_boosts_prepaid_giveaway_single(),
|
||||
tr::lng_boosts_prepaid_giveaway_status(
|
||||
lt_count,
|
||||
rpl::single(prepaid->quantity) | tr::to_count(),
|
||||
lt_duration,
|
||||
tr::lng_premium_gift_duration_months(
|
||||
prepaid->credits
|
||||
? tr::lng_boosts_prepaid_giveaway_credits_status(
|
||||
lt_count,
|
||||
rpl::single(prepaid->months) | tr::to_count())),
|
||||
rpl::single(prepaid->quantity) | tr::to_count(),
|
||||
lt_amount,
|
||||
tr::lng_prize_credits_amount(
|
||||
lt_count_decimal,
|
||||
rpl::single(prepaid->credits) | tr::to_count()))
|
||||
: tr::lng_boosts_prepaid_giveaway_status(
|
||||
lt_count,
|
||||
rpl::single(prepaid->quantity) | tr::to_count(),
|
||||
lt_duration,
|
||||
tr::lng_premium_gift_duration_months(
|
||||
lt_count,
|
||||
rpl::single(prepaid->months) | tr::to_count())),
|
||||
QImage())
|
||||
)->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
}
|
||||
@@ -337,24 +395,12 @@ void CreateGiveawayBox(
|
||||
object_ptr<Giveaway::GiveawayTypeRow>(
|
||||
box,
|
||||
GiveawayType::Random,
|
||||
tr::lng_giveaway_create_subtitle(),
|
||||
group));
|
||||
row->addRadio(typeGroup);
|
||||
row->setClickedCallback([=] {
|
||||
state->typeValue.force_assign(GiveawayType::Random);
|
||||
});
|
||||
}
|
||||
if (!prepaid) {
|
||||
const auto row = contentWrap->entity()->add(
|
||||
object_ptr<Giveaway::GiveawayTypeRow>(
|
||||
box,
|
||||
GiveawayType::SpecificUsers,
|
||||
state->toAwardAmountChanged.events_starting_with(
|
||||
rpl::empty_value()
|
||||
) | rpl::map([=] {
|
||||
const auto &selected = state->selectedToAward;
|
||||
return selected.empty()
|
||||
? tr::lng_giveaway_award_subtitle()
|
||||
? tr::lng_giveaway_create_subtitle()
|
||||
: (selected.size() == 1)
|
||||
? rpl::single(selected.front()->name())
|
||||
: tr::lng_giveaway_award_chosen(
|
||||
@@ -366,21 +412,33 @@ void CreateGiveawayBox(
|
||||
row->setClickedCallback([=] {
|
||||
auto initBox = [=](not_null<PeerListBox*> peersBox) {
|
||||
peersBox->setTitle(tr::lng_giveaway_award_option());
|
||||
|
||||
auto aboveOwned = object_ptr<Ui::VerticalLayout>(peersBox);
|
||||
const auto above = aboveOwned.data();
|
||||
peersBox->peerListSetAboveWidget(std::move(aboveOwned));
|
||||
Ui::AddSkip(above);
|
||||
const auto buttonRandom = above->add(
|
||||
object_ptr<Ui::SettingsButton>(
|
||||
peersBox,
|
||||
tr::lng_giveaway_random_button(),
|
||||
st::settingsButtonLightNoIcon));
|
||||
buttonRandom->setClickedCallback([=] {
|
||||
state->selectedToAward.clear();
|
||||
state->toAwardAmountChanged.fire({});
|
||||
state->typeValue.force_assign(GiveawayType::Random);
|
||||
peersBox->closeBox();
|
||||
});
|
||||
Ui::AddSkip(above);
|
||||
|
||||
peersBox->addButton(tr::lng_settings_save(), [=] {
|
||||
state->selectedToAward = peersBox->collectSelectedRows();
|
||||
state->toAwardAmountChanged.fire({});
|
||||
state->typeValue.force_assign(GiveawayType::Random);
|
||||
peersBox->closeBox();
|
||||
});
|
||||
peersBox->addButton(tr::lng_cancel(), [=] {
|
||||
peersBox->closeBox();
|
||||
});
|
||||
peersBox->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
state->typeValue.force_assign(
|
||||
state->selectedToAward.empty()
|
||||
? GiveawayType::Random
|
||||
: GiveawayType::SpecificUsers);
|
||||
}, peersBox->lifetime());
|
||||
};
|
||||
|
||||
using Controller = Giveaway::AwardMembersListController;
|
||||
@@ -398,6 +456,67 @@ void CreateGiveawayBox(
|
||||
Ui::LayerOption::KeepOther);
|
||||
});
|
||||
}
|
||||
const auto creditsOption = [=](int index) {
|
||||
const auto options = state->apiCreditsOptions.options();
|
||||
return (index >= 0 && index < options.size())
|
||||
? options[index]
|
||||
: Data::CreditsGiveawayOption();
|
||||
};
|
||||
const auto creditsOptionWinners = [=](int index) {
|
||||
const auto winners = creditsOption(index).winners;
|
||||
return ranges::views::all(
|
||||
winners
|
||||
) | ranges::views::transform([](const auto &w) {
|
||||
return w.users;
|
||||
}) | ranges::to_vector;
|
||||
};
|
||||
const auto creditsTypeWrap = contentWrap->entity()->add(
|
||||
object_ptr<Ui::VerticalLayout>(contentWrap->entity()));
|
||||
const auto fillCreditsTypeWrap = [=] {
|
||||
if (state->apiCreditsOptions.options().empty()) {
|
||||
return;
|
||||
}
|
||||
static constexpr auto kOutdated = 1735689600;
|
||||
|
||||
auto badge = [&] {
|
||||
if (base::unixtime::now() > kOutdated) {
|
||||
return QImage();
|
||||
}
|
||||
const auto badge = Ui::CreateChild<Ui::PaddingWrap<>>(
|
||||
creditsTypeWrap,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
creditsTypeWrap,
|
||||
tr::lng_premium_summary_new_badge(tr::now),
|
||||
st::settingsPremiumNewBadge),
|
||||
st::settingsPremiumNewBadgePadding);
|
||||
badge->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
badge->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(badge);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::windowBgActive);
|
||||
const auto r = st::settingsPremiumNewBadgePadding.left();
|
||||
p.drawRoundedRect(badge->rect(), r, r);
|
||||
}, badge->lifetime());
|
||||
badge->show();
|
||||
auto result = Ui::GrabWidget(badge).toImage();
|
||||
badge->hide();
|
||||
return result;
|
||||
}();
|
||||
|
||||
const auto row = creditsTypeWrap->add(
|
||||
object_ptr<Giveaway::GiveawayTypeRow>(
|
||||
box,
|
||||
GiveawayType::Credits,
|
||||
kColorIndexCredits,
|
||||
tr::lng_credits_summary_title(),
|
||||
tr::lng_giveaway_create_subtitle(),
|
||||
std::move(badge)));
|
||||
row->addRadio(typeGroup);
|
||||
row->setClickedCallback([=] {
|
||||
state->typeValue.force_assign(GiveawayType::Credits);
|
||||
});
|
||||
};
|
||||
|
||||
{
|
||||
const auto &padding = st::giveawayGiftCodeTypeDividerPadding;
|
||||
@@ -412,33 +531,239 @@ void CreateGiveawayBox(
|
||||
object_ptr<Ui::VerticalLayout>(box)));
|
||||
state->typeValue.value(
|
||||
) | rpl::start_with_next([=](GiveawayType type) {
|
||||
randomWrap->toggle(type == GiveawayType::Random, anim::type::instant);
|
||||
randomWrap->toggle(!isSpecificUsers(), anim::type::instant);
|
||||
}, randomWrap->lifetime());
|
||||
|
||||
randomWrap->toggleOn(
|
||||
state->typeValue.value(
|
||||
) | rpl::map(rpl::mappers::_1 == GiveawayType::Random),
|
||||
anim::type::instant);
|
||||
randomWrap->toggleOn(hideSpecificUsersOn(), anim::type::instant);
|
||||
|
||||
const auto sliderContainer = randomWrap->entity()->add(
|
||||
object_ptr<Ui::VerticalLayout>(randomWrap));
|
||||
const auto randomCreditsWrap = randomWrap->entity()->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
contentWrap,
|
||||
object_ptr<Ui::VerticalLayout>(box)));
|
||||
randomCreditsWrap->toggleOn(
|
||||
state->typeValue.value(
|
||||
) | rpl::map(rpl::mappers::_1 == GiveawayType::Credits),
|
||||
anim::type::instant);
|
||||
const auto fillCreditsOptions = [=] {
|
||||
randomCreditsWrap->entity()->clear();
|
||||
|
||||
const auto &st = st::giveawayTypeListItem;
|
||||
const auto &stButton = st::defaultSettingsButton;
|
||||
const auto &stStatus = st::defaultTextStyle;
|
||||
const auto buttonInnerSkip = st.height - stButton.height;
|
||||
const auto options = state->apiCreditsOptions.options();
|
||||
const auto content = randomCreditsWrap->entity();
|
||||
const auto title = Ui::AddSubsectionTitle(
|
||||
content,
|
||||
tr::lng_giveaway_credits_options_title());
|
||||
|
||||
const auto rightLabel = Ui::CreateChild<Ui::FlatLabel>(
|
||||
content,
|
||||
st::giveawayGiftCodeQuantitySubtitle);
|
||||
rightLabel->show();
|
||||
|
||||
rpl::combine(
|
||||
tr::lng_giveaway_quantity(
|
||||
lt_count,
|
||||
creditsGroup->value() | rpl::map([=](int i) -> float64 {
|
||||
return creditsOption(i).yearlyBoosts;
|
||||
})),
|
||||
title->positionValue(),
|
||||
content->geometryValue()
|
||||
) | rpl::start_with_next([=](QString s, const QPoint &p, QRect) {
|
||||
rightLabel->setText(std::move(s));
|
||||
rightLabel->moveToRight(st::boxRowPadding.right(), p.y());
|
||||
}, rightLabel->lifetime());
|
||||
|
||||
const auto buttonHeight = st.height;
|
||||
const auto minCredits = 0;
|
||||
|
||||
struct State final {
|
||||
rpl::variable<bool> isExtended = false;
|
||||
};
|
||||
const auto creditsState = content->lifetime().make_state<State>();
|
||||
|
||||
for (auto i = 0; i < options.size(); i++) {
|
||||
const auto &option = options[i];
|
||||
if (option.credits < minCredits) {
|
||||
continue;
|
||||
}
|
||||
struct State final {
|
||||
std::optional<Ui::Text::String> text;
|
||||
QString status;
|
||||
bool hasStatus = false;
|
||||
};
|
||||
const auto buttonWrap = content->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||
content,
|
||||
object_ptr<Ui::SettingsButton>(
|
||||
content,
|
||||
rpl::never<QString>(),
|
||||
stButton)));
|
||||
const auto button = buttonWrap->entity();
|
||||
button->setPaddingOverride({ 0, buttonInnerSkip, 0, 0 });
|
||||
const auto buttonState = button->lifetime().make_state<State>();
|
||||
buttonState->text.emplace(
|
||||
st.nameStyle,
|
||||
tr::lng_credits_summary_options_credits(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
option.credits));
|
||||
buttonState->status = tr::lng_giveaway_credits_option_status(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
option.credits);
|
||||
const auto price = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
Ui::FillAmountAndCurrency(option.amount, option.currency),
|
||||
st::creditsTopupPrice);
|
||||
const auto inner = Ui::CreateChild<Ui::RpWidget>(button);
|
||||
const auto stars = Ui::GenerateStars(
|
||||
st.nameStyle.font->height,
|
||||
(i + 1));
|
||||
const auto textLeft = st.photoPosition.x()
|
||||
+ (st.nameStyle.font->spacew * 2)
|
||||
+ (stars.width() / style::DevicePixelRatio());
|
||||
state->sliderValue.value(
|
||||
) | rpl::start_with_next([=](int users) {
|
||||
const auto option = creditsOption(i);
|
||||
buttonState->hasStatus = false;
|
||||
for (const auto &winner : option.winners) {
|
||||
if (winner.users == users) {
|
||||
auto status = tr::lng_giveaway_credits_option_status(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
winner.perUserStars);
|
||||
buttonState->status = std::move(status);
|
||||
buttonState->hasStatus = true;
|
||||
inner->update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
inner->update();
|
||||
}, button->lifetime());
|
||||
inner->paintRequest(
|
||||
) | rpl::start_with_next([=](const QRect &rect) {
|
||||
auto p = QPainter(inner);
|
||||
const auto namey = buttonState->hasStatus
|
||||
? st.namePosition.y()
|
||||
: (buttonHeight - stStatus.font->height) / 2;
|
||||
p.drawImage(st.photoPosition.x(), namey, stars);
|
||||
p.setPen(st.nameFg);
|
||||
buttonState->text->draw(p, {
|
||||
.position = QPoint(textLeft, namey),
|
||||
.availableWidth = inner->width() - textLeft,
|
||||
.elisionLines = 1,
|
||||
});
|
||||
if (buttonState->hasStatus) {
|
||||
p.setFont(stStatus.font);
|
||||
p.setPen(st.statusFg);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.drawText(
|
||||
st.photoPosition.x(),
|
||||
st.statusPosition.y() + stStatus.font->ascent,
|
||||
buttonState->status);
|
||||
}
|
||||
}, inner->lifetime());
|
||||
button->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
price->moveToRight(
|
||||
st::boxRowPadding.right(),
|
||||
(buttonHeight - price->height()) / 2);
|
||||
inner->moveToLeft(0, 0);
|
||||
inner->resize(
|
||||
width
|
||||
- price->width()
|
||||
- st::boxRowPadding.right()
|
||||
- st::boxRowPadding.left() / 2,
|
||||
buttonHeight);
|
||||
}, button->lifetime());
|
||||
|
||||
{
|
||||
const auto &st = st::defaultCheckbox;
|
||||
const auto radio = Ui::CreateChild<Ui::Radioenum<int>>(
|
||||
button,
|
||||
creditsGroup,
|
||||
i,
|
||||
QString(),
|
||||
st);
|
||||
radio->moveToLeft(
|
||||
st::boxRowPadding.left(),
|
||||
(buttonHeight - radio->checkRect().height()) / 2);
|
||||
radio->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
radio->show();
|
||||
}
|
||||
button->setClickedCallback([=] {
|
||||
creditsGroup->setValue(i);
|
||||
});
|
||||
if (option.isDefault) {
|
||||
creditsGroup->setValue(i);
|
||||
}
|
||||
buttonWrap->toggle(
|
||||
(!option.isExtended) || option.isDefault,
|
||||
anim::type::instant);
|
||||
if (option.isExtended) {
|
||||
buttonWrap->toggleOn(creditsState->isExtended.value());
|
||||
}
|
||||
Ui::ToggleChildrenVisibility(button, true);
|
||||
}
|
||||
|
||||
{
|
||||
Ui::AddSkip(content, st::settingsButton.padding.top());
|
||||
const auto showMoreWrap = Info::Statistics::AddShowMoreButton(
|
||||
content,
|
||||
tr::lng_stories_show_more());
|
||||
showMoreWrap->toggle(true, anim::type::instant);
|
||||
|
||||
showMoreWrap->entity()->setClickedCallback([=] {
|
||||
showMoreWrap->toggle(false, anim::type::instant);
|
||||
creditsState->isExtended = true;
|
||||
});
|
||||
}
|
||||
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddDividerText(content, tr::lng_giveaway_credits_options_about());
|
||||
Ui::AddSkip(content);
|
||||
};
|
||||
|
||||
const auto sliderContainerWrap = randomWrap->entity()->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
randomWrap,
|
||||
object_ptr<Ui::VerticalLayout>(randomWrap)));
|
||||
const auto sliderContainer = sliderContainerWrap->entity();
|
||||
sliderContainerWrap->toggle(true, anim::type::instant);
|
||||
const auto fillSliderContainer = [=] {
|
||||
const auto availablePresets = state->apiOptions.availablePresets();
|
||||
const auto creditsOptions = state->apiCreditsOptions.options();
|
||||
if (prepaid) {
|
||||
state->sliderValue = prepaid->quantity;
|
||||
return;
|
||||
}
|
||||
if (availablePresets.empty()) {
|
||||
if (availablePresets.empty()
|
||||
&& (creditsOptions.empty()
|
||||
|| creditsOptions.front().winners.empty())) {
|
||||
return;
|
||||
}
|
||||
state->sliderValue = availablePresets.front();
|
||||
state->sliderValue = availablePresets.empty()
|
||||
? creditsOptions.front().winners.front().users
|
||||
: availablePresets.front();
|
||||
auto creditsValueType = typeGroup->value(
|
||||
) | rpl::map(rpl::mappers::_1 == GiveawayType::Credits);
|
||||
const auto title = Ui::AddSubsectionTitle(
|
||||
sliderContainer,
|
||||
tr::lng_giveaway_quantity_title());
|
||||
rpl::conditional(
|
||||
rpl::duplicate(creditsValueType),
|
||||
tr::lng_giveaway_credits_quantity_title(),
|
||||
tr::lng_giveaway_quantity_title()));
|
||||
const auto rightLabel = Ui::CreateChild<Ui::FlatLabel>(
|
||||
sliderContainer,
|
||||
st::giveawayGiftCodeQuantitySubtitle);
|
||||
rightLabel->show();
|
||||
rpl::duplicate(
|
||||
creditsValueType
|
||||
) | rpl::start_with_next([=](bool isCredits) {
|
||||
rightLabel->setVisible(!isCredits);
|
||||
}, rightLabel->lifetime());
|
||||
|
||||
const auto floatLabel = Ui::CreateChild<Ui::FlatLabel>(
|
||||
sliderContainer,
|
||||
@@ -462,37 +787,87 @@ void CreateGiveawayBox(
|
||||
const auto &padding = st::giveawayGiftCodeSliderPadding;
|
||||
Ui::AddSkip(sliderContainer, padding.top());
|
||||
|
||||
const auto slider = sliderContainer->add(
|
||||
object_ptr<Ui::MediaSliderWheelless>(
|
||||
sliderContainer,
|
||||
st::settingsScale),
|
||||
const auto sliderParent = sliderContainer->add(
|
||||
object_ptr<Ui::VerticalLayout>(sliderContainer),
|
||||
st::boxRowPadding);
|
||||
struct State final {
|
||||
Ui::MediaSliderWheelless *slider = nullptr;
|
||||
};
|
||||
const auto sliderState = sliderParent->lifetime().make_state<State>();
|
||||
Ui::AddSkip(sliderContainer, padding.bottom());
|
||||
slider->resize(slider->width(), st::settingsScale.seekSize.height());
|
||||
slider->setPseudoDiscrete(
|
||||
availablePresets.size(),
|
||||
[=](int index) { return availablePresets[index]; },
|
||||
availablePresets.front(),
|
||||
[=](int boosts) { state->sliderValue = boosts; },
|
||||
[](int) {});
|
||||
rpl::combine(
|
||||
rpl::duplicate(creditsValueType),
|
||||
creditsGroup->value()
|
||||
) | rpl::start_with_next([=](bool isCredits, int value) {
|
||||
while (sliderParent->count()) {
|
||||
delete sliderParent->widgetAt(0);
|
||||
}
|
||||
sliderState->slider = sliderParent->add(
|
||||
object_ptr<Ui::MediaSliderWheelless>(
|
||||
sliderContainer,
|
||||
st::settingsScale));
|
||||
sliderState->slider->resize(
|
||||
sliderState->slider->width(),
|
||||
st::settingsScale.seekSize.height());
|
||||
const auto &values = isCredits
|
||||
? creditsOptionWinners(value)
|
||||
: availablePresets;
|
||||
const auto resultValue = [&] {
|
||||
const auto sliderValue = state->sliderValue.current();
|
||||
return ranges::contains(values, sliderValue)
|
||||
? sliderValue
|
||||
: values.front();
|
||||
}();
|
||||
state->sliderValue.force_assign(resultValue);
|
||||
if (values.size() <= 1) {
|
||||
sliderContainerWrap->toggle(false, anim::type::instant);
|
||||
return;
|
||||
} else {
|
||||
sliderContainerWrap->toggle(true, anim::type::instant);
|
||||
}
|
||||
sliderState->slider->setPseudoDiscrete(
|
||||
values.size(),
|
||||
[=](int index) { return values[index]; },
|
||||
resultValue,
|
||||
[=](int boosts) { state->sliderValue = boosts; },
|
||||
[](int) {});
|
||||
}, sliderParent->lifetime());
|
||||
|
||||
state->sliderValue.value(
|
||||
) | rpl::start_with_next([=](int boosts) {
|
||||
rpl::combine(
|
||||
rpl::duplicate(creditsValueType),
|
||||
creditsGroup->value(),
|
||||
state->sliderValue.value()
|
||||
) | rpl::start_with_next([=](
|
||||
bool isCredits,
|
||||
int credits,
|
||||
int boosts) {
|
||||
floatLabel->setText(QString::number(boosts));
|
||||
|
||||
const auto count = availablePresets.size();
|
||||
const auto sliderWidth = slider->width()
|
||||
if (!sliderState->slider) {
|
||||
return;
|
||||
}
|
||||
const auto &values = isCredits
|
||||
? creditsOptionWinners(credits)
|
||||
: availablePresets;
|
||||
const auto count = values.size();
|
||||
if (count <= 1) {
|
||||
return;
|
||||
}
|
||||
const auto sliderWidth = sliderState->slider->width()
|
||||
- st::settingsScale.seekSize.width();
|
||||
for (auto i = 0; i < count; i++) {
|
||||
if ((i + 1 == count || availablePresets[i + 1] > boosts)
|
||||
&& availablePresets[i] <= boosts) {
|
||||
if ((i + 1 == count || values[i + 1] > boosts)
|
||||
&& values[i] <= boosts) {
|
||||
const auto x = (sliderWidth * i) / (count - 1);
|
||||
const auto mapped = sliderState->slider->mapTo(
|
||||
sliderContainer,
|
||||
sliderState->slider->pos());
|
||||
floatLabel->moveToLeft(
|
||||
slider->x()
|
||||
mapped.x()
|
||||
+ x
|
||||
+ st::settingsScale.seekSize.width() / 2
|
||||
- floatLabel->width() / 2,
|
||||
slider->y()
|
||||
mapped.y()
|
||||
- floatLabel->height()
|
||||
- st::giveawayGiftCodeSliderFloatSkip);
|
||||
break;
|
||||
@@ -503,7 +878,10 @@ void CreateGiveawayBox(
|
||||
Ui::AddSkip(sliderContainer);
|
||||
Ui::AddDividerText(
|
||||
sliderContainer,
|
||||
tr::lng_giveaway_quantity_about());
|
||||
rpl::conditional(
|
||||
rpl::duplicate(creditsValueType),
|
||||
tr::lng_giveaway_credits_quantity_about(),
|
||||
tr::lng_giveaway_quantity_about()));
|
||||
Ui::AddSkip(sliderContainer);
|
||||
|
||||
sliderContainer->resizeToWidth(box->width());
|
||||
@@ -541,7 +919,9 @@ void CreateGiveawayBox(
|
||||
lt_count,
|
||||
state->sliderValue.value(
|
||||
) | rpl::map([=](int v) -> float64 {
|
||||
return state->apiOptions.giveawayBoostsPerPremium() * v;
|
||||
return (prepaid && prepaid->boosts)
|
||||
? prepaid->boosts
|
||||
: (state->apiOptions.giveawayBoostsPerPremium() * v);
|
||||
})));
|
||||
|
||||
using IconType = Settings::IconType;
|
||||
@@ -700,32 +1080,34 @@ void CreateGiveawayBox(
|
||||
while (listOptionsSpecific->count()) {
|
||||
delete listOptionsSpecific->widgetAt(0);
|
||||
}
|
||||
const auto listOptions = (type == GiveawayType::SpecificUsers)
|
||||
const auto listOptions = isSpecificUsers()
|
||||
? listOptionsSpecific
|
||||
: listOptionsRandom;
|
||||
Ui::AddSubsectionTitle(
|
||||
listOptions,
|
||||
tr::lng_giveaway_duration_title(
|
||||
lt_count,
|
||||
rpl::single(usersCount) | tr::to_count()),
|
||||
st::giveawayGiftCodeChannelsSubsectionPadding);
|
||||
Ui::Premium::AddGiftOptions(
|
||||
listOptions,
|
||||
durationGroup,
|
||||
state->apiOptions.options(usersCount),
|
||||
st::giveawayGiftCodeGiftOption,
|
||||
true);
|
||||
if (type != GiveawayType::Credits) {
|
||||
Ui::AddSubsectionTitle(
|
||||
listOptions,
|
||||
tr::lng_giveaway_duration_title(
|
||||
lt_count,
|
||||
rpl::single(usersCount) | tr::to_count()),
|
||||
st::giveawayGiftCodeChannelsSubsectionPadding);
|
||||
Ui::Premium::AddGiftOptions(
|
||||
listOptions,
|
||||
durationGroup,
|
||||
state->apiOptions.options(usersCount),
|
||||
st::giveawayGiftCodeGiftOption,
|
||||
true);
|
||||
|
||||
Ui::AddSkip(listOptions);
|
||||
Ui::AddSkip(listOptions);
|
||||
|
||||
auto termsContainer = object_ptr<Ui::VerticalLayout>(listOptions);
|
||||
addTerms(termsContainer.data());
|
||||
listOptions->add(object_ptr<Ui::DividerLabel>(
|
||||
listOptions,
|
||||
std::move(termsContainer),
|
||||
st::defaultBoxDividerLabelPadding));
|
||||
auto termsContainer = object_ptr<Ui::VerticalLayout>(listOptions);
|
||||
addTerms(termsContainer.data());
|
||||
listOptions->add(object_ptr<Ui::DividerLabel>(
|
||||
listOptions,
|
||||
std::move(termsContainer),
|
||||
st::defaultBoxDividerLabelPadding));
|
||||
|
||||
Ui::AddSkip(listOptions);
|
||||
Ui::AddSkip(listOptions);
|
||||
}
|
||||
|
||||
box->verticalLayout()->resizeToWidth(box->width());
|
||||
};
|
||||
@@ -735,9 +1117,9 @@ void CreateGiveawayBox(
|
||||
state->typeValue.value()
|
||||
) | rpl::start_with_next([=](int users, GiveawayType type) {
|
||||
typeGroup->setValue(type);
|
||||
rebuildListOptions(type, (type == GiveawayType::SpecificUsers)
|
||||
? state->selectedToAward.size()
|
||||
: users);
|
||||
rebuildListOptions(
|
||||
type,
|
||||
isSpecificUsers() ? state->selectedToAward.size() : users);
|
||||
}, box->lifetime());
|
||||
} else {
|
||||
typeGroup->setValue(GiveawayType::Random);
|
||||
@@ -793,7 +1175,7 @@ void CreateGiveawayBox(
|
||||
? (rpl::single(prepaid->months) | rpl::type_erased())
|
||||
: state->chosenMonths.value();
|
||||
const auto usersCountByType = [=](GiveawayType type) {
|
||||
if (type != GiveawayType::SpecificUsers) {
|
||||
if (!isSpecificUsers()) {
|
||||
return state->sliderValue.value() | rpl::type_erased();
|
||||
}
|
||||
return state->toAwardAmountChanged.events_starting_with_copy(
|
||||
@@ -853,8 +1235,49 @@ void CreateGiveawayBox(
|
||||
TextWithEntities{ duration },
|
||||
Ui::Text::RichLangValue);
|
||||
});
|
||||
auto creditsAdditionalAbout = rpl::combine(
|
||||
state->additionalPrize.value(),
|
||||
state->sliderValue.value(),
|
||||
creditsGroup->value()
|
||||
) | rpl::map([=](QString prize, int users, int creditsIndex) {
|
||||
const auto credits = creditsOption(creditsIndex).credits;
|
||||
return prize.isEmpty()
|
||||
? tr::lng_giveaway_prizes_just_credits(
|
||||
tr::now,
|
||||
lt_count,
|
||||
credits,
|
||||
Ui::Text::RichLangValue)
|
||||
: tr::lng_giveaway_prizes_additional_credits(
|
||||
tr::now,
|
||||
lt_count,
|
||||
users,
|
||||
lt_prize,
|
||||
TextWithEntities{ prize },
|
||||
lt_amount,
|
||||
tr::lng_giveaway_prizes_additional_credits_amount(
|
||||
tr::now,
|
||||
lt_count,
|
||||
credits,
|
||||
Ui::Text::RichLangValue),
|
||||
Ui::Text::RichLangValue);
|
||||
});
|
||||
|
||||
Ui::AddDividerText(additionalWrap, std::move(additionalAbout));
|
||||
auto creditsValueType = typeGroup->value(
|
||||
) | rpl::map(rpl::mappers::_1 == GiveawayType::Credits);
|
||||
|
||||
Ui::AddDividerText(
|
||||
additionalWrap,
|
||||
rpl::conditional(
|
||||
additionalToggle->toggledValue(),
|
||||
rpl::conditional(
|
||||
rpl::duplicate(creditsValueType),
|
||||
std::move(creditsAdditionalAbout),
|
||||
std::move(additionalAbout)),
|
||||
rpl::conditional(
|
||||
rpl::duplicate(creditsValueType),
|
||||
tr::lng_giveaway_additional_credits_about(),
|
||||
tr::lng_giveaway_additional_about()
|
||||
) | rpl::map(Ui::Text::WithEntities)));
|
||||
Ui::AddSkip(additionalWrap);
|
||||
}
|
||||
|
||||
@@ -955,14 +1378,26 @@ void CreateGiveawayBox(
|
||||
AddLabelWithBadgeToButton(
|
||||
button,
|
||||
rpl::conditional(
|
||||
state->typeValue.value(
|
||||
) | rpl::map(rpl::mappers::_1 == GiveawayType::Random),
|
||||
hideSpecificUsersOn(),
|
||||
tr::lng_giveaway_start(),
|
||||
tr::lng_giveaway_award()),
|
||||
state->sliderValue.value(
|
||||
) | rpl::map([=](int v) -> int {
|
||||
return state->apiOptions.giveawayBoostsPerPremium() * v;
|
||||
}),
|
||||
(prepaid && prepaid->boosts)
|
||||
? rpl::single(prepaid->boosts) | rpl::type_erased()
|
||||
: rpl::conditional(
|
||||
state->typeValue.value(
|
||||
) | rpl::map(rpl::mappers::_1 == GiveawayType::Credits),
|
||||
creditsGroup->value() | rpl::map([=](int v) {
|
||||
return creditsOption(v).yearlyBoosts;
|
||||
}),
|
||||
rpl::combine(
|
||||
state->sliderValue.value(),
|
||||
hideSpecificUsersOn()
|
||||
) | rpl::map([=](int value, bool random) -> int {
|
||||
return state->apiOptions.giveawayBoostsPerPremium()
|
||||
* (random
|
||||
? value
|
||||
: int(state->selectedToAward.size()));
|
||||
})),
|
||||
state->confirmButtonBusy.value() | rpl::map(!rpl::mappers::_1));
|
||||
|
||||
{
|
||||
@@ -985,19 +1420,40 @@ void CreateGiveawayBox(
|
||||
return;
|
||||
}
|
||||
const auto type = typeGroup->current();
|
||||
const auto isSpecific = (type == GiveawayType::SpecificUsers);
|
||||
const auto isSpecific = isSpecificUsers();
|
||||
const auto isRandom = (type == GiveawayType::Random);
|
||||
if (!isSpecific && !isRandom) {
|
||||
const auto isCredits = (type == GiveawayType::Credits);
|
||||
if (!isSpecific && !isRandom && !isCredits) {
|
||||
return;
|
||||
}
|
||||
auto invoice = state->apiOptions.invoice(
|
||||
isSpecific
|
||||
? state->selectedToAward.size()
|
||||
: state->sliderValue.current(),
|
||||
prepaid
|
||||
? prepaid->months
|
||||
: state->apiOptions.monthsFromPreset(
|
||||
durationGroup->current()));
|
||||
auto invoice = [&] {
|
||||
if (isPrepaidCredits) {
|
||||
return Payments::InvoicePremiumGiftCode{
|
||||
.creditsAmount = prepaid->credits,
|
||||
.randomId = prepaid->id,
|
||||
.users = prepaid->quantity,
|
||||
};
|
||||
} else if (isCredits) {
|
||||
const auto option = creditsOption(
|
||||
creditsGroup->current());
|
||||
return Payments::InvoicePremiumGiftCode{
|
||||
.currency = option.currency,
|
||||
.storeProduct = option.storeProduct,
|
||||
.creditsAmount = option.credits,
|
||||
.randomId = UniqueIdFromCreditsOption(option, peer),
|
||||
.amount = option.amount,
|
||||
.users = state->sliderValue.current(),
|
||||
};
|
||||
}
|
||||
return state->apiOptions.invoice(
|
||||
isSpecific
|
||||
? state->selectedToAward.size()
|
||||
: state->sliderValue.current(),
|
||||
prepaid
|
||||
? prepaid->months
|
||||
: state->apiOptions.monthsFromPreset(
|
||||
durationGroup->current()));
|
||||
}();
|
||||
if (isSpecific) {
|
||||
if (state->selectedToAward.empty()) {
|
||||
return;
|
||||
@@ -1011,7 +1467,7 @@ void CreateGiveawayBox(
|
||||
}) | ranges::to_vector,
|
||||
peer->asChannel(),
|
||||
};
|
||||
} else if (isRandom) {
|
||||
} else if (isRandom || isCredits || isPrepaidCredits) {
|
||||
invoice.purpose = Payments::InvoicePremiumGiftCodeGiveaway{
|
||||
.boostPeer = peer->asChannel(),
|
||||
.additionalChannels = ranges::views::all(
|
||||
@@ -1129,10 +1585,20 @@ void CreateGiveawayBox(
|
||||
if (!prepaid) {
|
||||
state->chosenMonths = state->apiOptions.monthsFromPreset(0);
|
||||
}
|
||||
fillCreditsTypeWrap();
|
||||
fillCreditsOptions();
|
||||
rebuildListOptions(state->typeValue.current(), 1);
|
||||
contentWrap->toggle(true, anim::type::instant);
|
||||
contentWrap->resizeToWidth(box->width());
|
||||
};
|
||||
const auto receivedOptions = [=] {
|
||||
state->lifetimeApi.destroy();
|
||||
state->lifetimeApi = state->apiCreditsOptions.request(
|
||||
) | rpl::start_with_error_done([=](const QString &error) {
|
||||
box->uiShow()->showToast(error);
|
||||
box->closeBox();
|
||||
}, done);
|
||||
};
|
||||
if (prepaid) {
|
||||
return done();
|
||||
}
|
||||
@@ -1140,6 +1606,6 @@ void CreateGiveawayBox(
|
||||
) | rpl::start_with_error_done([=](const QString &error) {
|
||||
box->uiShow()->showToast(error);
|
||||
box->closeBox();
|
||||
}, done);
|
||||
}, receivedOptions);
|
||||
}, box->lifetime());
|
||||
}
|
||||
|
||||
@@ -8,11 +8,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/channel_statistics/boosts/giveaway/giveaway_type_row.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/effects/credits_graphics.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_statistics.h"
|
||||
|
||||
@@ -33,7 +36,8 @@ GiveawayTypeRow::GiveawayTypeRow(
|
||||
(type == Type::SpecificUsers)
|
||||
? tr::lng_giveaway_award_option()
|
||||
: (type == Type::Random)
|
||||
? tr::lng_giveaway_create_option()
|
||||
? tr::lng_premium_summary_title()
|
||||
// ? tr::lng_giveaway_create_option()
|
||||
: (type == Type::AllMembers)
|
||||
? (group
|
||||
? tr::lng_giveaway_users_all_group()
|
||||
@@ -54,19 +58,27 @@ GiveawayTypeRow::GiveawayTypeRow(
|
||||
QImage badge)
|
||||
: RippleButton(parent, st::defaultRippleAnimation)
|
||||
, _type(type)
|
||||
, _st((_type == Type::SpecificUsers || _type == Type::Random)
|
||||
, _st((_type == Type::SpecificUsers
|
||||
|| _type == Type::Random
|
||||
|| _type == Type::Credits)
|
||||
? st::giveawayTypeListItem
|
||||
: (_type == Type::Prepaid)
|
||||
: ((_type == Type::Prepaid) || (_type == Type::PrepaidCredits))
|
||||
? st::boostsListBox.item
|
||||
: st::giveawayGiftCodeMembersPeerList.item)
|
||||
, _userpic(
|
||||
Ui::EmptyUserpic::UserpicColor(Ui::EmptyUserpic::ColorIndex(colorIndex)),
|
||||
QString())
|
||||
, _badge(std::move(badge)) {
|
||||
if (_type == Type::Credits || _type == Type::PrepaidCredits) {
|
||||
_customUserpic = Ui::CreditsWhiteDoubledIcon(_st.photoSize, 1.);
|
||||
}
|
||||
std::move(
|
||||
subtitle
|
||||
) | rpl::start_with_next([=] (const QString &s) {
|
||||
_status.setText(st::defaultTextStyle, s, Ui::NameTextOptions());
|
||||
) | rpl::start_with_next([=] (QString s) {
|
||||
_status.setText(
|
||||
st::defaultTextStyle,
|
||||
s.replace(QChar('>'), QString()),
|
||||
Ui::NameTextOptions());
|
||||
}, lifetime());
|
||||
std::move(
|
||||
title
|
||||
@@ -85,11 +97,13 @@ void GiveawayTypeRow::paintEvent(QPaintEvent *e) {
|
||||
const auto paintOver = (isOver() || isDown()) && !isDisabled();
|
||||
const auto skipRight = _st.photoPosition.x();
|
||||
const auto outerWidth = width();
|
||||
const auto isRandom = (_type == Type::Random);
|
||||
const auto isSpecific = (_type == Type::SpecificUsers);
|
||||
const auto isPrepaid = (_type == Type::Prepaid);
|
||||
const auto hasUserpic = (_type == Type::Random)
|
||||
const auto hasUserpic = isRandom
|
||||
|| isSpecific
|
||||
|| isPrepaid;
|
||||
|| isPrepaid
|
||||
|| (!_customUserpic.isNull());
|
||||
|
||||
if (paintOver) {
|
||||
p.fillRect(e->rect(), _st.button.textBgOver);
|
||||
@@ -103,16 +117,20 @@ void GiveawayTypeRow::paintEvent(QPaintEvent *e) {
|
||||
outerWidth,
|
||||
_st.photoSize);
|
||||
|
||||
const auto &userpic = isSpecific
|
||||
? st::giveawayUserpicGroup
|
||||
: st::giveawayUserpic;
|
||||
const auto userpicRect = QRect(
|
||||
_st.photoPosition
|
||||
- QPoint(
|
||||
isSpecific ? -st::giveawayUserpicSkip : 0,
|
||||
isSpecific ? 0 : st::giveawayUserpicSkip),
|
||||
Size(_st.photoSize));
|
||||
userpic.paintInCenter(p, userpicRect);
|
||||
if (!_customUserpic.isNull()) {
|
||||
p.drawImage(_st.photoPosition, _customUserpic);
|
||||
} else {
|
||||
const auto &userpic = isSpecific
|
||||
? st::giveawayUserpicGroup
|
||||
: st::giveawayUserpic;
|
||||
userpic.paintInCenter(p, userpicRect);
|
||||
}
|
||||
}
|
||||
|
||||
const auto namex = _st.namePosition.x();
|
||||
@@ -133,12 +151,29 @@ void GiveawayTypeRow::paintEvent(QPaintEvent *e) {
|
||||
_badge);
|
||||
}
|
||||
|
||||
const auto statusIcon = isRandom ? &st::topicButtonArrow : nullptr;
|
||||
const auto statusx = _st.statusPosition.x();
|
||||
const auto statusy = _st.statusPosition.y();
|
||||
const auto statusw = outerWidth - statusx - skipRight;
|
||||
const auto statusw = outerWidth
|
||||
- statusx
|
||||
- skipRight
|
||||
- (statusIcon
|
||||
? (statusIcon->width() + st::boostsListMiniIconSkip)
|
||||
: 0);
|
||||
p.setFont(st::contactsStatusFont);
|
||||
p.setPen((isSpecific || !hasUserpic) ? st::lightButtonFg : _st.statusFg);
|
||||
p.setPen((isRandom || !hasUserpic) ? st::lightButtonFg : _st.statusFg);
|
||||
_status.drawLeftElided(p, statusx, statusy, statusw, outerWidth);
|
||||
if (statusIcon) {
|
||||
statusIcon->paint(
|
||||
p,
|
||||
QPoint(
|
||||
statusx
|
||||
+ std::min(_status.maxWidth(), statusw)
|
||||
+ st::boostsListMiniIconSkip,
|
||||
statusy + st::contactsStatusFont->descent),
|
||||
outerWidth,
|
||||
st::lightButtonFg->c);
|
||||
}
|
||||
}
|
||||
|
||||
void GiveawayTypeRow::addRadio(
|
||||
|
||||
@@ -27,6 +27,9 @@ public:
|
||||
OnlyNewMembers,
|
||||
|
||||
Prepaid,
|
||||
PrepaidCredits,
|
||||
|
||||
Credits,
|
||||
};
|
||||
|
||||
GiveawayTypeRow(
|
||||
@@ -58,6 +61,8 @@ private:
|
||||
Ui::Text::String _status;
|
||||
Ui::Text::String _name;
|
||||
|
||||
QImage _customUserpic;
|
||||
|
||||
QImage _badge;
|
||||
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/statistics/info_statistics_inner_widget.h" // FillLoading.
|
||||
#include "info/statistics/info_statistics_list_controllers.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "statistics/widgets/chart_header_widget.h"
|
||||
#include "ui/boxes/boost_box.h"
|
||||
#include "ui/controls/invite_link_label.h"
|
||||
@@ -253,9 +254,11 @@ void FillGetBoostsButton(
|
||||
(st.height + rect::m::sum::v(st.padding) - icon.height()) / 2,
|
||||
})->show();
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddDividerText(content, peer->isMegagroup()
|
||||
? tr::lng_boosts_get_boosts_subtext_group()
|
||||
: tr::lng_boosts_get_boosts_subtext());
|
||||
Ui::AddDividerText(
|
||||
content,
|
||||
peer->isMegagroup()
|
||||
? tr::lng_boosts_get_boosts_subtext_group()
|
||||
: tr::lng_boosts_get_boosts_subtext());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -334,6 +337,7 @@ void InnerWidget::fill() {
|
||||
Ui::AddSkip(inner);
|
||||
|
||||
if (!status.prepaidGiveaway.empty()) {
|
||||
constexpr auto kColorIndexCredits = int(1);
|
||||
const auto multiplier = Api::PremiumGiftCodeOptions(_peer)
|
||||
.giveawayBoostsPerPremium();
|
||||
Ui::AddSkip(inner);
|
||||
@@ -343,17 +347,30 @@ void InnerWidget::fill() {
|
||||
using namespace Giveaway;
|
||||
const auto button = inner->add(object_ptr<GiveawayTypeRow>(
|
||||
inner,
|
||||
GiveawayTypeRow::Type::Prepaid,
|
||||
g.id,
|
||||
tr::lng_boosts_prepaid_giveaway_quantity(
|
||||
lt_count,
|
||||
rpl::single(g.quantity) | tr::to_count()),
|
||||
tr::lng_boosts_prepaid_giveaway_moths(
|
||||
lt_count,
|
||||
rpl::single(g.months) | tr::to_count()),
|
||||
g.credits
|
||||
? GiveawayTypeRow::Type::PrepaidCredits
|
||||
: GiveawayTypeRow::Type::Prepaid,
|
||||
g.credits ? kColorIndexCredits : g.id,
|
||||
g.credits
|
||||
? tr::lng_boosts_prepaid_giveaway_single()
|
||||
: tr::lng_boosts_prepaid_giveaway_quantity(
|
||||
lt_count,
|
||||
rpl::single(g.quantity) | tr::to_count()),
|
||||
g.credits
|
||||
? tr::lng_boosts_prepaid_giveaway_credits_status(
|
||||
lt_count,
|
||||
rpl::single(g.quantity) | tr::to_count(),
|
||||
lt_amount,
|
||||
tr::lng_prize_credits_amount(
|
||||
lt_count_decimal,
|
||||
rpl::single(g.credits) | tr::to_count()))
|
||||
: tr::lng_boosts_prepaid_giveaway_moths(
|
||||
lt_count,
|
||||
rpl::single(g.months) | tr::to_count()),
|
||||
Info::Statistics::CreateBadge(
|
||||
st::statisticsDetailsBottomCaptionStyle,
|
||||
QString::number(g.quantity * multiplier),
|
||||
QString::number(
|
||||
g.boosts ? g.boosts : (g.quantity * multiplier)),
|
||||
st::boostsListBadgeHeight,
|
||||
st::boostsListBadgeTextPadding,
|
||||
st::premiumButtonBg2,
|
||||
@@ -397,6 +414,12 @@ void InnerWidget::fill() {
|
||||
_controller->showPeerInfo(user);
|
||||
});
|
||||
}
|
||||
} else if (boost.credits) {
|
||||
_show->showBox(
|
||||
Box(
|
||||
::Settings::BoostCreditsBox,
|
||||
_controller->parentController(),
|
||||
boost));
|
||||
} else if (!boost.isUnclaimed) {
|
||||
_show->showToast(tr::lng_boosts_list_pending_about(tr::now));
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_credits.h"
|
||||
#include "api/api_statistics.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "chat_helpers/stickers_gift_box_pack.h"
|
||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_credits.h"
|
||||
@@ -46,6 +47,7 @@ namespace Info::Statistics {
|
||||
namespace {
|
||||
|
||||
using BoostCallback = Fn<void(const Data::Boost &)>;
|
||||
constexpr auto kColorIndexCredits = int(1);
|
||||
constexpr auto kColorIndexUnclaimed = int(3);
|
||||
constexpr auto kColorIndexPending = int(4);
|
||||
|
||||
@@ -478,6 +480,7 @@ private:
|
||||
Ui::EmptyUserpic _userpic;
|
||||
QImage _badge;
|
||||
QImage _rightBadge;
|
||||
PaintRoundImageCallback _paintUserpicCallback;
|
||||
|
||||
};
|
||||
|
||||
@@ -492,7 +495,9 @@ BoostRow::BoostRow(const Data::Boost &boost)
|
||||
: PeerListRow(UniqueRowIdFromString(boost.id))
|
||||
, _boost(boost)
|
||||
, _userpic(
|
||||
Ui::EmptyUserpic::UserpicColor(boost.isUnclaimed
|
||||
Ui::EmptyUserpic::UserpicColor(boost.credits
|
||||
? kColorIndexCredits
|
||||
: boost.isUnclaimed
|
||||
? kColorIndexUnclaimed
|
||||
: kColorIndexPending),
|
||||
QString()) {
|
||||
@@ -500,8 +505,41 @@ BoostRow::BoostRow(const Data::Boost &boost)
|
||||
}
|
||||
|
||||
void BoostRow::init() {
|
||||
if (!PeerListRow::special()) {
|
||||
_paintUserpicCallback = PeerListRow::generatePaintUserpicCallback(
|
||||
false);
|
||||
} else if (_boost.credits) {
|
||||
const auto creditsIcon = std::make_shared<QImage>();
|
||||
_paintUserpicCallback = [=](
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
int size) mutable {
|
||||
_userpic.paintCircle(p, x, y, outerWidth, size);
|
||||
if (creditsIcon->isNull()) {
|
||||
*creditsIcon = Ui::CreditsWhiteDoubledIcon(size, 1.);
|
||||
}
|
||||
p.drawImage(x, y, *creditsIcon);
|
||||
};
|
||||
} else {
|
||||
_paintUserpicCallback = [=](
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
int size) mutable {
|
||||
_userpic.paintCircle(p, x, y, outerWidth, size);
|
||||
(_boost.isUnclaimed
|
||||
? st::boostsListUnclaimedIcon
|
||||
: st::boostsListUnknownIcon).paintInCenter(
|
||||
p,
|
||||
Rect(x, y, Size(size)));
|
||||
};
|
||||
}
|
||||
|
||||
invalidateBadges();
|
||||
auto status = !PeerListRow::special()
|
||||
auto status = (!PeerListRow::special() || _boost.credits)
|
||||
? tr::lng_boosts_list_status(
|
||||
tr::now,
|
||||
lt_date,
|
||||
@@ -521,23 +559,18 @@ const Data::Boost &BoostRow::boost() const {
|
||||
QString BoostRow::generateName() {
|
||||
return !PeerListRow::special()
|
||||
? PeerListRow::generateName()
|
||||
: _boost.credits
|
||||
? tr::lng_giveaway_prizes_additional_credits_amount(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
_boost.credits)
|
||||
: _boost.isUnclaimed
|
||||
? tr::lng_boosts_list_unclaimed(tr::now)
|
||||
: tr::lng_boosts_list_pending(tr::now);
|
||||
}
|
||||
|
||||
PaintRoundImageCallback BoostRow::generatePaintUserpicCallback(bool force) {
|
||||
if (!PeerListRow::special()) {
|
||||
return PeerListRow::generatePaintUserpicCallback(force);
|
||||
}
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
||||
_userpic.paintCircle(p, x, y, outerWidth, size);
|
||||
(_boost.isUnclaimed
|
||||
? st::boostsListUnclaimedIcon
|
||||
: st::boostsListUnknownIcon).paintInCenter(
|
||||
p,
|
||||
Rect(x, y, Size(size)));
|
||||
};
|
||||
return _paintUserpicCallback;
|
||||
}
|
||||
|
||||
void BoostRow::invalidateBadges() {
|
||||
@@ -561,7 +594,7 @@ void BoostRow::invalidateBadges() {
|
||||
const auto &rightIcon = _boost.isGiveaway
|
||||
? st::boostsListGiveawayMiniIcon
|
||||
: st::boostsListGiftMiniIcon;
|
||||
_rightBadge = (_boost.isGift || _boost.isGiveaway)
|
||||
_rightBadge = ((_boost.isGift || _boost.isGiveaway) && !_boost.credits)
|
||||
? CreateBadge(
|
||||
st::boostsListRightBadgeTextStyle,
|
||||
_boost.isGiveaway
|
||||
@@ -810,7 +843,7 @@ void CreditsRow::init() {
|
||||
const auto name = !isSpecial
|
||||
? PeerListRow::generateName()
|
||||
: Ui::GenerateEntryName(_entry).text;
|
||||
_name = _entry.reaction
|
||||
_name = (_entry.reaction || _entry.bareGiveawayMsgId)
|
||||
? Ui::GenerateEntryName(_entry).text
|
||||
: _entry.title.isEmpty()
|
||||
? name
|
||||
@@ -1041,7 +1074,13 @@ void CreditsController::applySlice(const Data::CreditsStatusSlice &slice) {
|
||||
return std::make_unique<CreditsRow>(descriptor);
|
||||
}
|
||||
};
|
||||
|
||||
auto giftPacksRequested = false;
|
||||
for (const auto &item : slice.list) {
|
||||
if (item.bareGiveawayMsgId && !giftPacksRequested) {
|
||||
giftPacksRequested = true;
|
||||
session().giftBoxStickersPacks().load();
|
||||
}
|
||||
delegate()->peerListAppendRow(create(item, {}));
|
||||
}
|
||||
for (const auto &item : slice.subscriptions) {
|
||||
|
||||
@@ -6050,7 +6050,7 @@ void OverlayWidget::handleMouseRelease(
|
||||
} else if (_over == Over::Video && _down == Over::Video) {
|
||||
if (_stories) {
|
||||
_stories->contentPressed(false);
|
||||
} else if (_streamed) {
|
||||
} else if (_streamed && !_window->mousePressCancelled()) {
|
||||
playbackPauseResume();
|
||||
}
|
||||
} else if (_pressed) {
|
||||
|
||||
@@ -45,7 +45,7 @@ inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> s
|
||||
inputMediaDice#e66fbf7b emoticon:string = InputMedia;
|
||||
inputMediaStory#89fdd778 peer:InputPeer id:int = InputMedia;
|
||||
inputMediaWebPage#c21b8849 flags:# force_large_media:flags.0?true force_small_media:flags.1?true optional:flags.2?true url:string = InputMedia;
|
||||
inputMediaPaidMedia#aa661fc3 stars_amount:long extended_media:Vector<InputMedia> = InputMedia;
|
||||
inputMediaPaidMedia#c4103386 flags:# stars_amount:long extended_media:Vector<InputMedia> payload:flags.0?string = InputMedia;
|
||||
|
||||
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
|
||||
inputChatUploadedPhoto#bdcdaec0 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.3?VideoSize = InputChatPhoto;
|
||||
@@ -133,8 +133,8 @@ messageMediaGeoLive#b940c666 flags:# geo:GeoPoint heading:flags.0?int period:int
|
||||
messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia;
|
||||
messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia;
|
||||
messageMediaStory#68cb6283 flags:# via_mention:flags.1?true peer:Peer id:int story:flags.0?StoryItem = MessageMedia;
|
||||
messageMediaGiveaway#daad85b0 flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.2?true channels:Vector<long> countries_iso2:flags.1?Vector<string> prize_description:flags.3?string quantity:int months:int until_date:int = MessageMedia;
|
||||
messageMediaGiveawayResults#c6991068 flags:# only_new_subscribers:flags.0?true refunded:flags.2?true channel_id:long additional_peers_count:flags.3?int launch_msg_id:int winners_count:int unclaimed_count:int winners:Vector<long> months:int prize_description:flags.1?string until_date:int = MessageMedia;
|
||||
messageMediaGiveaway#aa073beb flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.2?true channels:Vector<long> countries_iso2:flags.1?Vector<string> prize_description:flags.3?string quantity:int months:flags.4?int stars:flags.5?long until_date:int = MessageMedia;
|
||||
messageMediaGiveawayResults#ceaa3ea1 flags:# only_new_subscribers:flags.0?true refunded:flags.2?true channel_id:long additional_peers_count:flags.3?int launch_msg_id:int winners_count:int unclaimed_count:int winners:Vector<long> months:flags.4?int stars:flags.5?long prize_description:flags.1?string until_date:int = MessageMedia;
|
||||
messageMediaPaidMedia#a8852491 stars_amount:long extended_media:Vector<MessageExtendedMedia> = MessageMedia;
|
||||
|
||||
messageActionEmpty#b6aef7b0 = MessageAction;
|
||||
@@ -176,12 +176,13 @@ messageActionSuggestProfilePhoto#57de635e photo:Photo = MessageAction;
|
||||
messageActionRequestedPeer#31518e9b button_id:int peers:Vector<Peer> = MessageAction;
|
||||
messageActionSetChatWallPaper#5060a3f4 flags:# same:flags.0?true for_both:flags.1?true wallpaper:WallPaper = MessageAction;
|
||||
messageActionGiftCode#678c2e09 flags:# via_giveaway:flags.0?true unclaimed:flags.2?true boost_peer:flags.1?Peer months:int slug:string currency:flags.2?string amount:flags.2?long crypto_currency:flags.3?string crypto_amount:flags.3?long = MessageAction;
|
||||
messageActionGiveawayLaunch#332ba9ed = MessageAction;
|
||||
messageActionGiveawayResults#2a9fadc5 winners_count:int unclaimed_count:int = MessageAction;
|
||||
messageActionGiveawayLaunch#a80f51e4 flags:# stars:flags.0?long = MessageAction;
|
||||
messageActionGiveawayResults#87e2f155 flags:# stars:flags.0?true winners_count:int unclaimed_count:int = MessageAction;
|
||||
messageActionBoostApply#cc02aa6d boosts:int = MessageAction;
|
||||
messageActionRequestedPeerSentMe#93b31848 button_id:int peers:Vector<RequestedPeer> = MessageAction;
|
||||
messageActionPaymentRefunded#41b3e202 flags:# peer:Peer currency:string total_amount:long payload:flags.0?bytes charge:PaymentCharge = MessageAction;
|
||||
messageActionGiftStars#45d5b021 flags:# currency:string amount:long stars:long crypto_currency:flags.0?string crypto_amount:flags.0?long transaction_id:flags.1?string = MessageAction;
|
||||
messageActionPrizeStars#b00c47a2 flags:# unclaimed:flags.0?true stars:long transaction_id:string boost_peer:Peer giveaway_msg_id:int = MessageAction;
|
||||
|
||||
dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog;
|
||||
dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
|
||||
@@ -422,6 +423,8 @@ updateBroadcastRevenueTransactions#dfd961f5 peer:Peer balances:BroadcastRevenueB
|
||||
updateStarsBalance#fb85198 balance:long = Update;
|
||||
updateBusinessBotCallbackQuery#1ea2fda7 flags:# query_id:long user_id:long connection_id:string message:Message reply_to_message:flags.2?Message chat_instance:long data:flags.0?bytes = Update;
|
||||
updateStarsRevenueStatus#a584b019 peer:Peer status:StarsRevenueStatus = Update;
|
||||
updateBotPurchasedPaidMedia#283bd312 user_id:long payload:string qts:int = Update;
|
||||
updatePaidReactionPrivacy#51ca7aec private:Bool = Update;
|
||||
|
||||
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
|
||||
|
||||
@@ -1013,12 +1016,13 @@ channelAdminLogEventActionChangeWallpaper#31bb5d52 prev_value:WallPaper new_valu
|
||||
channelAdminLogEventActionChangeEmojiStatus#3ea9feb1 prev_value:EmojiStatus new_value:EmojiStatus = ChannelAdminLogEventAction;
|
||||
channelAdminLogEventActionChangeEmojiStickerSet#46d840ab prev_stickerset:InputStickerSet new_stickerset:InputStickerSet = ChannelAdminLogEventAction;
|
||||
channelAdminLogEventActionToggleSignatureProfiles#60a79c79 new_value:Bool = ChannelAdminLogEventAction;
|
||||
channelAdminLogEventActionParticipantSubExtend#64642db3 prev_participant:ChannelParticipant new_participant:ChannelParticipant = ChannelAdminLogEventAction;
|
||||
|
||||
channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent;
|
||||
|
||||
channels.adminLogResults#ed8af74d events:Vector<ChannelAdminLogEvent> chats:Vector<Chat> users:Vector<User> = channels.AdminLogResults;
|
||||
|
||||
channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true send:flags.16?true forums:flags.17?true = ChannelAdminLogEventsFilter;
|
||||
channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true send:flags.16?true forums:flags.17?true sub_extend:flags.18?true = ChannelAdminLogEventsFilter;
|
||||
|
||||
popularContact#5ce14175 client_id:long importers:int = PopularContact;
|
||||
|
||||
@@ -1472,6 +1476,7 @@ inputStorePaymentPremiumGiftCode#a3805f3f flags:# users:Vector<InputUser> boost_
|
||||
inputStorePaymentPremiumGiveaway#160544ca flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.3?true boost_peer:InputPeer additional_peers:flags.1?Vector<InputPeer> countries_iso2:flags.2?Vector<string> prize_description:flags.4?string random_id:long until_date:int currency:string amount:long = InputStorePaymentPurpose;
|
||||
inputStorePaymentStarsTopup#dddd0f56 stars:long currency:string amount:long = InputStorePaymentPurpose;
|
||||
inputStorePaymentStarsGift#1d741ef7 user_id:InputUser stars:long currency:string amount:long = InputStorePaymentPurpose;
|
||||
inputStorePaymentStarsGiveaway#751f08fa flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.3?true stars:long boost_peer:InputPeer additional_peers:flags.1?Vector<InputPeer> countries_iso2:flags.2?Vector<string> prize_description:flags.4?string random_id:long until_date:int currency:string amount:long users:int = InputStorePaymentPurpose;
|
||||
|
||||
premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption;
|
||||
|
||||
@@ -1633,11 +1638,12 @@ premiumGiftCodeOption#257e962b flags:# users:int months:int store_product:flags.
|
||||
payments.checkedGiftCode#284a1096 flags:# via_giveaway:flags.2?true from_id:flags.4?Peer giveaway_msg_id:flags.3?int to_id:flags.0?long date:int months:int used_date:flags.1?int chats:Vector<Chat> users:Vector<User> = payments.CheckedGiftCode;
|
||||
|
||||
payments.giveawayInfo#4367daa0 flags:# participating:flags.0?true preparing_results:flags.3?true start_date:int joined_too_early_date:flags.1?int admin_disallowed_chat_id:flags.2?long disallowed_country:flags.4?string = payments.GiveawayInfo;
|
||||
payments.giveawayInfoResults#cd5570 flags:# winner:flags.0?true refunded:flags.1?true start_date:int gift_code_slug:flags.0?string finish_date:int winners_count:int activated_count:int = payments.GiveawayInfo;
|
||||
payments.giveawayInfoResults#e175e66f flags:# winner:flags.0?true refunded:flags.1?true start_date:int gift_code_slug:flags.3?string stars_prize:flags.4?long finish_date:int winners_count:int activated_count:flags.2?int = payments.GiveawayInfo;
|
||||
|
||||
prepaidGiveaway#b2539d54 id:long months:int quantity:int date:int = PrepaidGiveaway;
|
||||
prepaidStarsGiveaway#9a9d77e0 id:long stars:long quantity:int boosts:int date:int = PrepaidGiveaway;
|
||||
|
||||
boost#2a1c8c71 flags:# gift:flags.1?true giveaway:flags.2?true unclaimed:flags.3?true id:string user_id:flags.0?long giveaway_msg_id:flags.2?int date:int expires:int used_gift_slug:flags.4?string multiplier:flags.5?int = Boost;
|
||||
boost#4b3e14d6 flags:# gift:flags.1?true giveaway:flags.2?true unclaimed:flags.3?true id:string user_id:flags.0?long giveaway_msg_id:flags.2?int date:int expires:int used_gift_slug:flags.4?string multiplier:flags.5?int stars:flags.6?long = Boost;
|
||||
|
||||
premium.boostsList#86f8613c flags:# count:int boosts:Vector<Boost> next_offset:flags.0?string users:Vector<User> = premium.BoostsList;
|
||||
|
||||
@@ -1795,7 +1801,7 @@ reactionNotificationsFromAll#4b9e22a0 = ReactionNotificationsFrom;
|
||||
|
||||
reactionsNotifySettings#56e34970 flags:# messages_notify_from:flags.0?ReactionNotificationsFrom stories_notify_from:flags.1?ReactionNotificationsFrom sound:NotificationSound show_previews:Bool = ReactionsNotifySettings;
|
||||
|
||||
broadcastRevenueBalances#8438f1c6 current_balance:long available_balance:long overall_revenue:long = BroadcastRevenueBalances;
|
||||
broadcastRevenueBalances#c3ff71e7 flags:# withdrawal_enabled:flags.0?true current_balance:long available_balance:long overall_revenue:long = BroadcastRevenueBalances;
|
||||
|
||||
availableEffect#93c3e27e flags:# premium_required:flags.2?true id:long emoticon:string static_icon_id:flags.0?long effect_sticker_id:long effect_animation_id:flags.1?long = AvailableEffect;
|
||||
|
||||
@@ -1814,7 +1820,7 @@ starsTransactionPeerAds#60682812 = StarsTransactionPeer;
|
||||
|
||||
starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption;
|
||||
|
||||
starsTransaction#433aeb2b flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector<MessageMedia> subscription_period:flags.12?int = StarsTransaction;
|
||||
starsTransaction#ee7522d5 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector<MessageMedia> subscription_period:flags.12?int giveaway_post_id:flags.13?int = StarsTransaction;
|
||||
|
||||
payments.starsStatus#bbfa316c flags:# balance:long subscriptions:flags.1?Vector<StarsSubscription> subscriptions_next_offset:flags.2?string subscriptions_missing_balance:flags.4?long history:flags.3?Vector<StarsTransaction> next_offset:flags.0?string chats:Vector<Chat> users:Vector<User> = payments.StarsStatus;
|
||||
|
||||
@@ -1848,6 +1854,10 @@ starsSubscription#538ecf18 flags:# canceled:flags.0?true can_refulfill:flags.1?t
|
||||
|
||||
messageReactor#4ba3a95a flags:# top:flags.0?true my:flags.1?true anonymous:flags.2?true peer_id:flags.3?Peer count:int = MessageReactor;
|
||||
|
||||
starsGiveawayOption#94ce852a flags:# extended:flags.0?true default:flags.1?true stars:long yearly_boosts:int store_product:flags.2?string currency:string amount:long winners:Vector<StarsGiveawayWinnersOption> = StarsGiveawayOption;
|
||||
|
||||
starsGiveawayWinnersOption#54236209 flags:# default:flags.0?true users:int per_user_stars:long = StarsGiveawayWinnersOption;
|
||||
|
||||
---functions---
|
||||
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||
@@ -2244,8 +2254,9 @@ messages.editFactCheck#589ee75 peer:InputPeer msg_id:int text:TextWithEntities =
|
||||
messages.deleteFactCheck#d1da940c peer:InputPeer msg_id:int = Updates;
|
||||
messages.getFactCheck#b9cdc5ee peer:InputPeer msg_id:Vector<int> = Vector<FactCheck>;
|
||||
messages.requestMainWebView#c9e01e7b flags:# compact:flags.7?true peer:InputPeer bot:InputUser start_param:flags.1?string theme_params:flags.0?DataJSON platform:string = WebViewResult;
|
||||
messages.sendPaidReaction#25c8fe3e flags:# private:flags.0?true peer:InputPeer msg_id:int count:int random_id:long = Updates;
|
||||
messages.sendPaidReaction#9dd6a67b flags:# peer:InputPeer msg_id:int count:int random_id:long private:flags.0?Bool = Updates;
|
||||
messages.togglePaidReactionPrivacy#849ad397 peer:InputPeer msg_id:int private:Bool = Bool;
|
||||
messages.getPaidReactionPrivacy#472455aa = Updates;
|
||||
|
||||
updates.getState#edd4882a = updates.State;
|
||||
updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference;
|
||||
@@ -2411,6 +2422,7 @@ payments.getStarsGiftOptions#d3c96bc8 flags:# user_id:flags.0?InputUser = Vector
|
||||
payments.getStarsSubscriptions#32512c5 flags:# missing_balance:flags.0?true peer:InputPeer offset:string = payments.StarsStatus;
|
||||
payments.changeStarsSubscription#c7770878 flags:# peer:InputPeer subscription_id:string canceled:flags.0?Bool = Bool;
|
||||
payments.fulfillStarsSubscription#cc5bebb3 peer:InputPeer subscription_id:string = Bool;
|
||||
payments.getStarsGiveawayOptions#bd1efd3e = Vector<StarsGiveawayOption>;
|
||||
|
||||
stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> software:flags.3?string = messages.StickerSet;
|
||||
stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet;
|
||||
@@ -2530,4 +2542,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool;
|
||||
|
||||
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;
|
||||
|
||||
// LAYER 186
|
||||
// LAYER 187
|
||||
|
||||
@@ -173,6 +173,49 @@ MTPinputStorePaymentPurpose InvoicePremiumGiftCodeGiveawayToTL(
|
||||
MTP_long(invoice.amount));
|
||||
}
|
||||
|
||||
MTPinputStorePaymentPurpose InvoiceCreditsGiveawayToTL(
|
||||
const InvoicePremiumGiftCode &invoice) {
|
||||
Expects(invoice.creditsAmount.has_value());
|
||||
const auto &giveaway = v::get<InvoicePremiumGiftCodeGiveaway>(
|
||||
invoice.purpose);
|
||||
using Flag = MTPDinputStorePaymentStarsGiveaway::Flag;
|
||||
return MTP_inputStorePaymentStarsGiveaway(
|
||||
MTP_flags(Flag()
|
||||
| (giveaway.onlyNewSubscribers
|
||||
? Flag::f_only_new_subscribers
|
||||
: Flag())
|
||||
| (giveaway.additionalChannels.empty()
|
||||
? Flag()
|
||||
: Flag::f_additional_peers)
|
||||
| (giveaway.countries.empty()
|
||||
? Flag()
|
||||
: Flag::f_countries_iso2)
|
||||
| (giveaway.showWinners
|
||||
? Flag::f_winners_are_visible
|
||||
: Flag())
|
||||
| (giveaway.additionalPrize.isEmpty()
|
||||
? Flag()
|
||||
: Flag::f_prize_description)),
|
||||
MTP_long(*invoice.creditsAmount),
|
||||
giveaway.boostPeer->input,
|
||||
MTP_vector_from_range(ranges::views::all(
|
||||
giveaway.additionalChannels
|
||||
) | ranges::views::transform([](not_null<ChannelData*> c) {
|
||||
return MTPInputPeer(c->input);
|
||||
})),
|
||||
MTP_vector_from_range(ranges::views::all(
|
||||
giveaway.countries
|
||||
) | ranges::views::transform([](QString value) {
|
||||
return MTP_string(value);
|
||||
})),
|
||||
MTP_string(giveaway.additionalPrize),
|
||||
MTP_long(invoice.randomId),
|
||||
MTP_int(giveaway.untilDate),
|
||||
MTP_string(invoice.currency),
|
||||
MTP_long(invoice.amount),
|
||||
MTP_int(invoice.users));
|
||||
}
|
||||
|
||||
Form::Form(InvoiceId id, bool receipt)
|
||||
: _id(id)
|
||||
, _session(SessionFromId(id))
|
||||
@@ -335,6 +378,9 @@ MTPInputInvoice Form::inputInvoice() const {
|
||||
MTP_long(credits->amount)));
|
||||
}
|
||||
const auto &giftCode = v::get<InvoicePremiumGiftCode>(_id.value);
|
||||
if (giftCode.creditsAmount) {
|
||||
return MTP_inputInvoiceStars(InvoiceCreditsGiveawayToTL(giftCode));
|
||||
}
|
||||
using Flag = MTPDpremiumGiftCodeOption::Flag;
|
||||
const auto option = MTP_premiumGiftCodeOption(
|
||||
MTP_flags((giftCode.storeQuantity ? Flag::f_store_quantity : Flag())
|
||||
|
||||
@@ -150,10 +150,11 @@ struct InvoicePremiumGiftCode {
|
||||
InvoicePremiumGiftCodeUsers,
|
||||
InvoicePremiumGiftCodeGiveaway> purpose;
|
||||
|
||||
uint64 randomId = 0;
|
||||
QString currency;
|
||||
uint64 amount = 0;
|
||||
QString storeProduct;
|
||||
std::optional<uint64> creditsAmount;
|
||||
uint64 randomId = 0;
|
||||
uint64 amount = 0;
|
||||
int storeQuantity = 0;
|
||||
int users = 0;
|
||||
int months = 0;
|
||||
@@ -268,6 +269,8 @@ struct FormUpdate : std::variant<
|
||||
|
||||
[[nodiscard]] MTPinputStorePaymentPurpose InvoicePremiumGiftCodeGiveawayToTL(
|
||||
const InvoicePremiumGiftCode &invoice);
|
||||
[[nodiscard]] MTPinputStorePaymentPurpose InvoiceCreditsGiveawayToTL(
|
||||
const InvoicePremiumGiftCode &invoice);
|
||||
|
||||
class Form final : public base::has_weak_ptr {
|
||||
public:
|
||||
|
||||
@@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "payments/payments_reaction_process.h"
|
||||
|
||||
#include "api/api_credits.h"
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "apiwrap.h"
|
||||
#include "boxes/send_credits_box.h" // CreditsEmojiSmall.
|
||||
#include "core/ui_integration.h" // MarkedTextContext.
|
||||
#include "data/components/credits.h"
|
||||
@@ -43,7 +45,7 @@ void TryAddingPaidReaction(
|
||||
FullMsgId itemId,
|
||||
base::weak_ptr<HistoryView::Element> weakView,
|
||||
int count,
|
||||
bool anonymous,
|
||||
std::optional<bool> anonymous,
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
Fn<void(bool)> finished) {
|
||||
const auto checkItem = [=] {
|
||||
@@ -103,7 +105,7 @@ void TryAddingPaidReaction(
|
||||
not_null<HistoryItem*> item,
|
||||
HistoryView::Element *view,
|
||||
int count,
|
||||
bool anonymous,
|
||||
std::optional<bool> anonymous,
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
Fn<void(bool)> finished) {
|
||||
TryAddingPaidReaction(
|
||||
@@ -228,6 +230,9 @@ void ShowPaidReactionDetails(
|
||||
add(entry);
|
||||
entry.peer = nullptr;
|
||||
add(entry);
|
||||
if (session->api().globalPrivacy().paidReactionAnonymousCurrent()) {
|
||||
std::swap(top.front(), top.back());
|
||||
}
|
||||
}
|
||||
ranges::sort(top, ranges::greater(), &Ui::PaidReactionTop::count);
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ void TryAddingPaidReaction(
|
||||
not_null<HistoryItem*> item,
|
||||
HistoryView::Element *view,
|
||||
int count,
|
||||
bool anonymous,
|
||||
std::optional<bool> anonymous,
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
Fn<void(bool)> finished = nullptr);
|
||||
|
||||
|
||||
@@ -141,10 +141,12 @@ paymentsLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
|
||||
}
|
||||
|
||||
botWebViewPanelSize: size(384px, 694px);
|
||||
botWebViewBottomPadding: margins(12px, 12px, 12px, 12px);
|
||||
botWebViewBottomSkip: point(12px, 8px);
|
||||
botWebViewBottomButton: RoundButton(paymentsPanelSubmit) {
|
||||
height: 56px;
|
||||
height: 40px;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: boxButtonFont;
|
||||
}
|
||||
textTop: 19px;
|
||||
textTop: 11px;
|
||||
}
|
||||
|
||||
@@ -560,9 +560,9 @@ filterLinkPreviewRadius: 13px;
|
||||
filterLinkPreviewTop: 30px;
|
||||
filterLinkPreviewColumn: 65px;
|
||||
filterLinkPreviewAllBottom: 18px;
|
||||
filterLinkPreviewAllTop: 17px;
|
||||
filterLinkPreviewAllTop: 15px;
|
||||
filterLinkPreviewMyBottom: 74px;
|
||||
filterLinkPreviewMyTop: 73px;
|
||||
filterLinkPreviewMyTop: 71px;
|
||||
filterLinkPreviewChatSize: 36px;
|
||||
filterLinkPreviewChatSkip: 10px;
|
||||
filterLinkPreviewBadgeLeft: 40px;
|
||||
|
||||
@@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/click_handler_types.h" // UrlClickHandler
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_boosts.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_file_origin.h"
|
||||
@@ -45,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/credits_graphics.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/effects/premium_stars_colored.h"
|
||||
#include "ui/effects/premium_top_bar.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
@@ -498,6 +500,145 @@ not_null<Ui::RpWidget*> AddBalanceWidget(
|
||||
return balance;
|
||||
}
|
||||
|
||||
void BoostCreditsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Data::Boost &b) {
|
||||
box->setStyle(st::giveawayGiftCodeBox);
|
||||
box->setNoContentMargin(true);
|
||||
|
||||
const auto content = box->verticalLayout();
|
||||
const auto session = &controller->session();
|
||||
Ui::AddSkip(content);
|
||||
{
|
||||
const auto &stUser = st::premiumGiftsUserpicButton;
|
||||
const auto widget = content->add(object_ptr<Ui::RpWidget>(content));
|
||||
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
|
||||
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
|
||||
widget,
|
||||
false,
|
||||
Ui::Premium::MiniStars::Type::BiStars);
|
||||
stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
|
||||
widget->resize(
|
||||
st::boxWidth - stUser.photoSize,
|
||||
stUser.photoSize * 1.3);
|
||||
const auto svg = std::make_shared<QSvgRenderer>(
|
||||
Ui::Premium::ColorizedSvg(
|
||||
Ui::Premium::CreditsIconGradientStops()));
|
||||
content->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
widget->moveToLeft(stUser.photoSize / 2, 0);
|
||||
const auto starsRect = Rect(widget->size());
|
||||
stars->setPosition(starsRect.topLeft());
|
||||
stars->setSize(starsRect.size());
|
||||
widget->lower();
|
||||
}, widget->lifetime());
|
||||
widget->paintRequest(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
auto p = QPainter(widget);
|
||||
p.fillRect(r, Qt::transparent);
|
||||
stars->paint(p);
|
||||
svg->render(
|
||||
&p,
|
||||
QRectF(
|
||||
(widget->width() - stUser.photoSize) / 2.,
|
||||
(widget->height() - stUser.photoSize) / 2.,
|
||||
stUser.photoSize,
|
||||
stUser.photoSize));
|
||||
}, widget->lifetime());
|
||||
}
|
||||
content->add(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
content,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
tr::lng_gift_stars_title(
|
||||
lt_count,
|
||||
rpl::single(float64(b.credits))),
|
||||
st::boxTitle)));
|
||||
Ui::AddSkip(content);
|
||||
if (b.multiplier) {
|
||||
const auto &st = st::statisticsDetailsBottomCaptionStyle;
|
||||
const auto badge = content->add(object_ptr<Ui::RpWidget>(content));
|
||||
badge->resize(badge->width(), st.font->height * 1.5);
|
||||
const auto text = badge->lifetime().make_state<Ui::Text::String>(
|
||||
st::boxWidth
|
||||
- st::boxRowPadding.left()
|
||||
- st::boxRowPadding.right());
|
||||
auto textWithEntities = TextWithEntities();
|
||||
textWithEntities.append(
|
||||
Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
st::boostsListMiniIcon,
|
||||
{ st.font->descent * 2, st.font->descent / 2, 0, 0 },
|
||||
true)));
|
||||
textWithEntities.append(
|
||||
tr::lng_boosts_list_title(tr::now, lt_count, b.multiplier));
|
||||
text->setMarkedText(
|
||||
st,
|
||||
std::move(textWithEntities),
|
||||
kMarkupTextOptions,
|
||||
Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [=] { badge->update(); },
|
||||
});
|
||||
badge->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(badge);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto radius = badge->height() / 2;
|
||||
const auto badgeWidth = text->maxWidth() + radius;
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::premiumButtonBg2);
|
||||
p.drawRoundedRect(
|
||||
QRect(
|
||||
(badge->width() - badgeWidth) / 2,
|
||||
0,
|
||||
badgeWidth,
|
||||
badge->height()),
|
||||
radius,
|
||||
radius);
|
||||
p.setPen(st::premiumButtonFg);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
text->draw(p, Ui::Text::PaintContext{
|
||||
.position = QPoint(
|
||||
(badge->width() - text->maxWidth() - radius) / 2,
|
||||
(badge->height() - text->minHeight()) / 2),
|
||||
.outerWidth = badge->width(),
|
||||
.availableWidth = badge->width(),
|
||||
});
|
||||
}, badge->lifetime());
|
||||
|
||||
Ui::AddSkip(content);
|
||||
}
|
||||
AddCreditsBoostTable(controller, content, b);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
box->addRow(object_ptr<Ui::CenterWrap<>>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_credits_box_out_about(
|
||||
lt_link,
|
||||
tr::lng_payments_terms_link(
|
||||
) | Ui::Text::ToLink(
|
||||
tr::lng_credits_box_out_about_link(tr::now)),
|
||||
Ui::Text::WithEntities),
|
||||
st::creditsBoxAboutDivider)));
|
||||
Ui::AddSkip(content);
|
||||
|
||||
const auto button = box->addButton(tr::lng_box_ok(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
const auto buttonWidth = st::boxWidth
|
||||
- rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding);
|
||||
button->widthValue() | rpl::filter([=] {
|
||||
return (button->widthNoMargins() != buttonWidth);
|
||||
}) | rpl::start_with_next([=] {
|
||||
button->resizeToWidth(buttonWidth);
|
||||
}, button->lifetime());
|
||||
}
|
||||
|
||||
void ReceiptCreditsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -515,7 +656,10 @@ void ReceiptCreditsBox(
|
||||
|
||||
const auto &stUser = st::boostReplaceUserpic;
|
||||
const auto session = &controller->session();
|
||||
const auto peer = (s.barePeerId)
|
||||
const auto isPrize = e.bareGiveawayMsgId > 0;
|
||||
const auto peer = isPrize
|
||||
? nullptr
|
||||
: (s.barePeerId)
|
||||
? session->data().peer(PeerId(s.barePeerId)).get()
|
||||
: (e.peerType == Type::PremiumBot)
|
||||
? nullptr
|
||||
@@ -531,7 +675,7 @@ void ReceiptCreditsBox(
|
||||
content->add(object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::UserpicButton>(content, peer, stUser)));
|
||||
} else if (e.gift) {
|
||||
} else if (e.gift || isPrize) {
|
||||
struct State final {
|
||||
DocumentData *sticker = nullptr;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
@@ -612,6 +756,8 @@ void ReceiptCreditsBox(
|
||||
box,
|
||||
rpl::single(!s.until.isNull()
|
||||
? tr::lng_credits_box_subscription_title(tr::now)
|
||||
: isPrize
|
||||
? tr::lng_credits_box_history_entry_giveaway_name(tr::now)
|
||||
: !e.subscriptionUntil.isNull()
|
||||
? tr::lng_credits_box_history_entry_subscription(tr::now)
|
||||
: !e.title.isEmpty()
|
||||
@@ -745,7 +891,7 @@ void ReceiptCreditsBox(
|
||||
rpl::single(e.description),
|
||||
st::creditsBoxAbout)));
|
||||
}
|
||||
if (e.gift) {
|
||||
if (e.gift || isPrize) {
|
||||
Ui::AddSkip(content);
|
||||
const auto arrow = Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
@@ -759,7 +905,7 @@ void ReceiptCreditsBox(
|
||||
) | rpl::map([](TextWithEntities text) {
|
||||
return Ui::Text::Link(
|
||||
std::move(text),
|
||||
tr::lng_credits_box_history_entry_gift_about_url(tr::now));
|
||||
u"internal:stars_examples"_q);
|
||||
});
|
||||
box->addRow(object_ptr<Ui::CenterWrap<>>(
|
||||
box,
|
||||
|
||||
@@ -13,6 +13,7 @@ class object_ptr;
|
||||
class PeerData;
|
||||
|
||||
namespace Data {
|
||||
struct Boost;
|
||||
struct CreditsHistoryEntry;
|
||||
struct SubscriptionEntry;
|
||||
} // namespace Data
|
||||
@@ -74,6 +75,10 @@ void ReceiptCreditsBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Data::CreditsHistoryEntry &e,
|
||||
const Data::SubscriptionEntry &s);
|
||||
void BoostCreditsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Data::Boost &b);
|
||||
void GiftedCreditsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
|
||||
@@ -133,16 +133,11 @@ private:
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] bool UseSeparateWindow() {
|
||||
return !Platform::IsWayland()
|
||||
&& Ui::Platform::TranslucentWindowsSupported();
|
||||
}
|
||||
|
||||
Preview::Preview(QWidget *slider, rpl::producer<QImage> userpic)
|
||||
: _widget(slider->window())
|
||||
, _slider(slider)
|
||||
, _ratio(style::DevicePixelRatio())
|
||||
, _window(UseSeparateWindow()) {
|
||||
, _window(Ui::Platform::TranslucentWindowsSupported()) {
|
||||
std::move(userpic) | rpl::start_with_next([=](QImage &&userpic) {
|
||||
_userpicOriginal = std::move(userpic);
|
||||
if (!_userpicImage.isNull()) {
|
||||
|
||||
@@ -172,7 +172,8 @@ boostsListRightBadgeHeight: 20px;
|
||||
boostsListGiftMiniIconPadding: margins(4px, 2px, 0px, 0px);
|
||||
boostsListGiftMiniIcon: icon{{ "boosts/mini_gift", historyPeer8UserpicBg2 }};
|
||||
boostsListGiveawayMiniIcon: icon{{ "boosts/mini_giveaway", historyPeer4UserpicBg2 }};
|
||||
boostsListUnclaimedIcon: icon{{ "boosts/boost_unknown", premiumButtonFg }};
|
||||
boostsListUnknownIcon: icon{{ "boosts/boost_unclaimed", premiumButtonFg }};
|
||||
boostsListUnclaimedIcon: icon{{ "boosts/boost_unclaimed", premiumButtonFg }};
|
||||
boostsListUnknownIcon: icon{{ "boosts/boost_unknown", premiumButtonFg }};
|
||||
boostsListCreditsIconSize: 13px;
|
||||
|
||||
statisticsCurrencyIcon: icon {{ "statistics/mini_currency_graph", windowSubTextFg }};
|
||||
|
||||
@@ -152,9 +152,13 @@ MimeDataState ComputeMimeDataState(const QMimeData *data) {
|
||||
} else if (allAreSmallImages) {
|
||||
if (filesize > Images::kReadBytesLimit) {
|
||||
allAreSmallImages = false;
|
||||
} else if (!FileIsImage(file, MimeTypeForFile(info).name())
|
||||
|| !QImageReader(file).canRead()) {
|
||||
allAreSmallImages = false;
|
||||
} else {
|
||||
const auto mime = MimeTypeForFile(info).name();
|
||||
if (mime == u"image/gif"_q
|
||||
|| !FileIsImage(file, mime)
|
||||
|| !QImageReader(file).canRead()) {
|
||||
allAreSmallImages = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,26 @@ constexpr auto kProgressOpacity = 0.3;
|
||||
constexpr auto kLightnessThreshold = 128;
|
||||
constexpr auto kLightnessDelta = 32;
|
||||
|
||||
struct ButtonArgs {
|
||||
bool isActive = false;
|
||||
bool isVisible = false;
|
||||
bool isProgressVisible = false;
|
||||
QString text;
|
||||
};
|
||||
|
||||
[[nodiscard]] RectPart ParsePosition(const QString &position) {
|
||||
if (position == u"left"_q) {
|
||||
return RectPart::Left;
|
||||
} else if (position == u"top"_q) {
|
||||
return RectPart::Top;
|
||||
} else if (position == u"right"_q) {
|
||||
return RectPart::Right;
|
||||
} else if (position == u"bottom"_q) {
|
||||
return RectPart::Bottom;
|
||||
}
|
||||
return RectPart::Left;
|
||||
}
|
||||
|
||||
[[nodiscard]] QJsonObject ParseMethodArgs(const QString &json) {
|
||||
if (json.isEmpty()) {
|
||||
return {};
|
||||
@@ -99,6 +119,15 @@ constexpr auto kLightnessDelta = 32;
|
||||
alpha);
|
||||
}
|
||||
|
||||
[[nodiscard]] const style::color *LookupNamedColor(const QString &key) {
|
||||
if (key == u"secondary_bg_color"_q) {
|
||||
return &st::boxDividerBg;
|
||||
} else if (key == u"bottom_bar_bg_color"_q) {
|
||||
return &st::windowBg;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class Panel::Button final : public RippleButton {
|
||||
@@ -107,8 +136,11 @@ public:
|
||||
~Button();
|
||||
|
||||
void updateBg(QColor bg);
|
||||
void updateBg(not_null<const style::color*> paletteBg);
|
||||
void updateFg(QColor fg);
|
||||
void updateArgs(MainButtonArgs &&args);
|
||||
void updateFg(not_null<const style::color*> paletteFg);
|
||||
|
||||
void updateArgs(ButtonArgs &&args);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
@@ -128,6 +160,9 @@ private:
|
||||
style::owned_color _bg;
|
||||
RoundRect _roundRect;
|
||||
|
||||
rpl::lifetime _bgLifetime;
|
||||
rpl::lifetime _fgLifetime;
|
||||
|
||||
};
|
||||
|
||||
struct Panel::Progress {
|
||||
@@ -171,15 +206,33 @@ Panel::Button::~Button() = default;
|
||||
void Panel::Button::updateBg(QColor bg) {
|
||||
_bg.update(bg);
|
||||
_roundRect.setColor(_bg.color());
|
||||
_bgLifetime.destroy();
|
||||
update();
|
||||
}
|
||||
|
||||
void Panel::Button::updateBg(not_null<const style::color*> paletteBg) {
|
||||
updateBg((*paletteBg)->c);
|
||||
_bgLifetime = style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateBg((*paletteBg)->c);
|
||||
});
|
||||
}
|
||||
|
||||
void Panel::Button::updateFg(QColor fg) {
|
||||
_fg = fg;
|
||||
_fgLifetime.destroy();
|
||||
update();
|
||||
}
|
||||
|
||||
void Panel::Button::updateArgs(MainButtonArgs &&args) {
|
||||
void Panel::Button::updateFg(not_null<const style::color*> paletteFg) {
|
||||
updateFg((*paletteFg)->c);
|
||||
_fgLifetime = style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateFg((*paletteFg)->c);
|
||||
});
|
||||
}
|
||||
|
||||
void Panel::Button::updateArgs(ButtonArgs &&args) {
|
||||
_textFull = std::move(args.text);
|
||||
setDisabled(!args.isActive);
|
||||
setPointerCursor(false);
|
||||
@@ -266,10 +319,7 @@ void Panel::Button::setupProgressGeometry() {
|
||||
void Panel::Button::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
_roundRect.paintSomeRounded(
|
||||
p,
|
||||
rect().marginsAdded({ 0, st::callRadius * 2, 0, 0 }),
|
||||
RectPart::BottomLeft | RectPart::BottomRight);
|
||||
_roundRect.paint(p, rect());
|
||||
|
||||
if (!isDisabled()) {
|
||||
const auto ripple = ResolveRipple(_bg.color()->c);
|
||||
@@ -292,12 +342,7 @@ void Panel::Button::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
|
||||
QImage Panel::Button::prepareRippleMask() const {
|
||||
return RippleAnimation::MaskByDrawer(size(), false, [&](QPainter &p) {
|
||||
p.drawRoundedRect(
|
||||
rect().marginsAdded({ 0, st::callRadius * 2, 0, 0 }),
|
||||
st::callRadius,
|
||||
st::callRadius);
|
||||
});
|
||||
return RippleAnimation::RoundRectMask(size(), st::callRadius);
|
||||
}
|
||||
|
||||
QPoint Panel::Button::prepareRippleStartPosition() const {
|
||||
@@ -599,7 +644,7 @@ void Panel::createWebviewBottom() {
|
||||
) | rpl::start_with_next([=](QRect inner, int height) {
|
||||
bottom->move(inner.x(), inner.y() + inner.height() - height);
|
||||
bottom->resizeToWidth(inner.width());
|
||||
updateFooterHeight();
|
||||
layoutButtons();
|
||||
}, bottom->lifetime());
|
||||
}
|
||||
|
||||
@@ -633,7 +678,9 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) {
|
||||
}
|
||||
if (_webviewBottom.get() == bottom) {
|
||||
_webviewBottom = nullptr;
|
||||
_secondaryButton = nullptr;
|
||||
_mainButton = nullptr;
|
||||
_bottomButtonsBg = nullptr;
|
||||
}
|
||||
});
|
||||
if (!raw->widget()) {
|
||||
@@ -655,7 +702,6 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) {
|
||||
});
|
||||
});
|
||||
|
||||
updateFooterHeight();
|
||||
rpl::combine(
|
||||
container->geometryValue(),
|
||||
_footerHeight.value()
|
||||
@@ -681,7 +727,9 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) {
|
||||
} else if (command == "web_app_switch_inline_query") {
|
||||
switchInlineQueryMessage(arguments);
|
||||
} else if (command == "web_app_setup_main_button") {
|
||||
processMainButtonMessage(arguments);
|
||||
processButtonMessage(_mainButton, arguments);
|
||||
} else if (command == "web_app_setup_secondary_button") {
|
||||
processButtonMessage(_secondaryButton, arguments);
|
||||
} else if (command == "web_app_setup_back_button") {
|
||||
processBackButtonMessage(arguments);
|
||||
} else if (command == "web_app_setup_settings_button") {
|
||||
@@ -714,6 +762,8 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) {
|
||||
requestClipboardText(arguments);
|
||||
} else if (command == "web_app_set_header_color") {
|
||||
processHeaderColor(arguments);
|
||||
} else if (command == "web_app_set_bottom_bar_color") {
|
||||
processBottomBarColor(arguments);
|
||||
} else if (command == "share_score") {
|
||||
_delegate->botHandleMenuButton(MenuButton::ShareGame);
|
||||
}
|
||||
@@ -745,6 +795,7 @@ postEvent: function(eventType, eventData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
layoutButtons();
|
||||
setupProgressGeometry();
|
||||
|
||||
base::qt_signal_producer(
|
||||
@@ -1096,12 +1147,12 @@ void Panel::requestClipboardText(const QJsonObject &args) {
|
||||
}
|
||||
|
||||
bool Panel::allowOpenLink() const {
|
||||
const auto now = crl::now();
|
||||
if (_mainButtonLastClick
|
||||
&& _mainButtonLastClick + kProcessClickTimeout >= now) {
|
||||
_mainButtonLastClick = 0;
|
||||
return true;
|
||||
}
|
||||
//const auto now = crl::now();
|
||||
//if (_mainButtonLastClick
|
||||
// && _mainButtonLastClick + kProcessClickTimeout >= now) {
|
||||
// _mainButtonLastClick = 0;
|
||||
// return true;
|
||||
//}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1109,12 +1160,12 @@ bool Panel::allowClipboardQuery() const {
|
||||
if (!_allowClipboardRead) {
|
||||
return false;
|
||||
}
|
||||
const auto now = crl::now();
|
||||
if (_mainButtonLastClick
|
||||
&& _mainButtonLastClick + kProcessClickTimeout >= now) {
|
||||
_mainButtonLastClick = 0;
|
||||
return true;
|
||||
}
|
||||
//const auto now = crl::now();
|
||||
//if (_mainButtonLastClick
|
||||
// && _mainButtonLastClick + kProcessClickTimeout >= now) {
|
||||
// _mainButtonLastClick = 0;
|
||||
// return true;
|
||||
//}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1157,14 +1208,16 @@ void Panel::setupClosingBehaviour(const QJsonObject &args) {
|
||||
_closeNeedConfirmation = args["need_confirmation"].toBool();
|
||||
}
|
||||
|
||||
void Panel::processMainButtonMessage(const QJsonObject &args) {
|
||||
void Panel::processButtonMessage(
|
||||
std::unique_ptr<Button> &button,
|
||||
const QJsonObject &args) {
|
||||
if (args.isEmpty()) {
|
||||
_delegate->botClose();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto shown = [&] {
|
||||
return _mainButton && !_mainButton->isHidden();
|
||||
return button && !button->isHidden();
|
||||
};
|
||||
const auto wasShown = shown();
|
||||
const auto guard = gsl::finally([&] {
|
||||
@@ -1175,42 +1228,38 @@ void Panel::processMainButtonMessage(const QJsonObject &args) {
|
||||
}
|
||||
});
|
||||
|
||||
if (!_mainButton) {
|
||||
if (args["is_visible"].toBool()) {
|
||||
createMainButton();
|
||||
const auto text = args["text"].toString().trimmed();
|
||||
const auto visible = args["is_visible"].toBool() && !text.isEmpty();
|
||||
if (!button) {
|
||||
if (visible) {
|
||||
createButton(button);
|
||||
_bottomButtonsBg->show();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto bg = ParseColor(args["color"].toString())) {
|
||||
_mainButton->updateBg(*bg);
|
||||
_bgLifetime.destroy();
|
||||
button->updateBg(*bg);
|
||||
} else {
|
||||
_mainButton->updateBg(st::windowBgActive->c);
|
||||
_bgLifetime = style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
_mainButton->updateBg(st::windowBgActive->c);
|
||||
});
|
||||
button->updateBg(&st::windowBgActive);
|
||||
}
|
||||
|
||||
if (const auto fg = ParseColor(args["text_color"].toString())) {
|
||||
_mainButton->updateFg(*fg);
|
||||
_fgLifetime.destroy();
|
||||
button->updateFg(*fg);
|
||||
} else {
|
||||
_mainButton->updateFg(st::windowFgActive->c);
|
||||
_fgLifetime = style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
_mainButton->updateFg(st::windowFgActive->c);
|
||||
});
|
||||
button->updateFg(&st::windowFgActive);
|
||||
}
|
||||
|
||||
_mainButton->updateArgs({
|
||||
button->updateArgs({
|
||||
.isActive = args["is_active"].toBool(),
|
||||
.isVisible = args["is_visible"].toBool(),
|
||||
.isVisible = visible,
|
||||
.isProgressVisible = args["is_progress_visible"].toBool(),
|
||||
.text = args["text"].toString(),
|
||||
});
|
||||
if (button.get() == _secondaryButton.get()) {
|
||||
_secondaryPosition = ParsePosition(args["position"].toString());
|
||||
}
|
||||
}
|
||||
|
||||
void Panel::processBackButtonMessage(const QJsonObject &args) {
|
||||
@@ -1225,11 +1274,12 @@ void Panel::processHeaderColor(const QJsonObject &args) {
|
||||
if (const auto color = ParseColor(args["color"].toString())) {
|
||||
_widget->overrideTitleColor(color);
|
||||
_headerColorLifetime.destroy();
|
||||
} else if (args["color_key"].toString() == u"secondary_bg_color"_q) {
|
||||
_widget->overrideTitleColor(st::boxDividerBg->c);
|
||||
} else if (const auto color = LookupNamedColor(
|
||||
args["color_key"].toString())) {
|
||||
_widget->overrideTitleColor((*color)->c);
|
||||
_headerColorLifetime = style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
_widget->overrideTitleColor(st::boxDividerBg->c);
|
||||
_widget->overrideTitleColor((*color)->c);
|
||||
});
|
||||
} else {
|
||||
_widget->overrideTitleColor(std::nullopt);
|
||||
@@ -1237,37 +1287,146 @@ void Panel::processHeaderColor(const QJsonObject &args) {
|
||||
}
|
||||
}
|
||||
|
||||
void Panel::createMainButton() {
|
||||
_mainButton = std::make_unique<Button>(
|
||||
_widget.get(),
|
||||
st::botWebViewBottomButton);
|
||||
const auto button = _mainButton.get();
|
||||
|
||||
button->setClickedCallback([=] {
|
||||
if (!button->isDisabled()) {
|
||||
postEvent("main_button_pressed");
|
||||
_mainButtonLastClick = crl::now();
|
||||
}
|
||||
});
|
||||
button->hide();
|
||||
|
||||
rpl::combine(
|
||||
_webviewParent->geometryValue() | rpl::map([=] {
|
||||
return _widget->innerGeometry();
|
||||
}),
|
||||
button->shownValue(),
|
||||
button->heightValue()
|
||||
) | rpl::start_with_next([=](QRect inner, bool shown, int height) {
|
||||
button->move(inner.x(), inner.y() + inner.height() - height);
|
||||
button->resizeToWidth(inner.width());
|
||||
_webviewBottom->setVisible(!shown);
|
||||
updateFooterHeight();
|
||||
}, button->lifetime());
|
||||
void Panel::processBottomBarColor(const QJsonObject &args) {
|
||||
if (const auto color = ParseColor(args["color"].toString())) {
|
||||
_widget->overrideBottomBarColor(color);
|
||||
_bottomBarColor = color;
|
||||
_bottomBarColorLifetime.destroy();
|
||||
} else if (const auto color = LookupNamedColor(
|
||||
args["color_key"].toString())) {
|
||||
_widget->overrideBottomBarColor((*color)->c);
|
||||
_bottomBarColor = (*color)->c;
|
||||
_headerColorLifetime = style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
_widget->overrideBottomBarColor((*color)->c);
|
||||
_bottomBarColor = (*color)->c;
|
||||
});
|
||||
} else {
|
||||
_widget->overrideBottomBarColor(std::nullopt);
|
||||
_bottomBarColor = std::nullopt;
|
||||
_headerColorLifetime.destroy();
|
||||
}
|
||||
if (const auto raw = _bottomButtonsBg.get()) {
|
||||
raw->update();
|
||||
}
|
||||
}
|
||||
|
||||
void Panel::updateFooterHeight() {
|
||||
_footerHeight = (_mainButton && !_mainButton->isHidden())
|
||||
? _mainButton->height()
|
||||
void Panel::createButton(std::unique_ptr<Button> &button) {
|
||||
if (!_bottomButtonsBg) {
|
||||
_bottomButtonsBg = std::make_unique<RpWidget>(_widget.get());
|
||||
|
||||
const auto raw = _bottomButtonsBg.get();
|
||||
raw->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(raw);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(_bottomBarColor.value_or(st::windowBg->c));
|
||||
p.drawRoundedRect(
|
||||
raw->rect().marginsAdded({ 0, 2 * st::callRadius, 0, 0 }),
|
||||
st::callRadius,
|
||||
st::callRadius);
|
||||
}, raw->lifetime());
|
||||
}
|
||||
button = std::make_unique<Button>(
|
||||
_bottomButtonsBg.get(),
|
||||
st::botWebViewBottomButton);
|
||||
const auto raw = button.get();
|
||||
|
||||
raw->setClickedCallback([=] {
|
||||
if (!raw->isDisabled()) {
|
||||
if (raw == _mainButton.get()) {
|
||||
postEvent("main_button_pressed");
|
||||
} else if (raw == _secondaryButton.get()) {
|
||||
postEvent("secondary_button_pressed");
|
||||
}
|
||||
}
|
||||
});
|
||||
raw->hide();
|
||||
|
||||
rpl::combine(
|
||||
raw->shownValue(),
|
||||
raw->heightValue()
|
||||
) | rpl::start_with_next([=] {
|
||||
layoutButtons();
|
||||
}, raw->lifetime());
|
||||
}
|
||||
|
||||
void Panel::layoutButtons() {
|
||||
const auto inner = _widget->innerGeometry();
|
||||
const auto shown = [](std::unique_ptr<Button> &button) {
|
||||
return button && !button->isHidden();
|
||||
};
|
||||
const auto any = shown(_mainButton) || shown(_secondaryButton);
|
||||
_webviewBottom->setVisible(!any);
|
||||
if (any) {
|
||||
_bottomButtonsBg->show();
|
||||
|
||||
const auto one = shown(_mainButton)
|
||||
? _mainButton.get()
|
||||
: _secondaryButton.get();
|
||||
const auto both = shown(_mainButton) && shown(_secondaryButton);
|
||||
const auto vertical = both
|
||||
&& ((_secondaryPosition == RectPart::Top)
|
||||
|| (_secondaryPosition == RectPart::Bottom));
|
||||
const auto padding = st::botWebViewBottomPadding;
|
||||
const auto height = padding.top()
|
||||
+ (vertical
|
||||
? (_mainButton->height()
|
||||
+ st::botWebViewBottomSkip.y()
|
||||
+ _secondaryButton->height())
|
||||
: one->height())
|
||||
+ padding.bottom();
|
||||
_bottomButtonsBg->setGeometry(
|
||||
inner.x(),
|
||||
inner.y() + inner.height() - height,
|
||||
inner.width(),
|
||||
height);
|
||||
auto left = padding.left();
|
||||
auto bottom = height - padding.bottom();
|
||||
auto available = inner.width() - padding.left() - padding.right();
|
||||
if (!both) {
|
||||
one->resizeToWidth(available);
|
||||
one->move(left, bottom - one->height());
|
||||
} else if (_secondaryPosition == RectPart::Top) {
|
||||
_mainButton->resizeToWidth(available);
|
||||
bottom -= _mainButton->height();
|
||||
_mainButton->move(left, bottom);
|
||||
bottom -= st::botWebViewBottomSkip.y();
|
||||
_secondaryButton->resizeToWidth(available);
|
||||
bottom -= _secondaryButton->height();
|
||||
_secondaryButton->move(left, bottom);
|
||||
} else if (_secondaryPosition == RectPart::Bottom) {
|
||||
_secondaryButton->resizeToWidth(available);
|
||||
bottom -= _secondaryButton->height();
|
||||
_secondaryButton->move(left, bottom);
|
||||
bottom -= st::botWebViewBottomSkip.y();
|
||||
_mainButton->resizeToWidth(available);
|
||||
bottom -= _mainButton->height();
|
||||
_mainButton->move(left, bottom);
|
||||
} else if (_secondaryPosition == RectPart::Left) {
|
||||
available = (available - st::botWebViewBottomSkip.x()) / 2;
|
||||
_secondaryButton->resizeToWidth(available);
|
||||
bottom -= _secondaryButton->height();
|
||||
_secondaryButton->move(left, bottom);
|
||||
_mainButton->resizeToWidth(available);
|
||||
_mainButton->move(
|
||||
inner.width() - padding.right() - available,
|
||||
bottom);
|
||||
} else {
|
||||
available = (available - st::botWebViewBottomSkip.x()) / 2;
|
||||
_mainButton->resizeToWidth(available);
|
||||
bottom -= _mainButton->height();
|
||||
_mainButton->move(left, bottom);
|
||||
_secondaryButton->resizeToWidth(available);
|
||||
_secondaryButton->move(
|
||||
inner.width() - padding.right() - available,
|
||||
bottom);
|
||||
}
|
||||
} else if (_bottomButtonsBg) {
|
||||
_bottomButtonsBg->hide();
|
||||
}
|
||||
_footerHeight = any
|
||||
? _bottomButtonsBg->height()
|
||||
: _webviewBottom->height();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/object_ptr.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/flags.h"
|
||||
#include "ui/rect_part.h"
|
||||
#include "ui/round_rect.h"
|
||||
#include "webview/webview_common.h"
|
||||
|
||||
class QJsonObject;
|
||||
@@ -32,13 +34,6 @@ namespace Ui::BotWebView {
|
||||
|
||||
[[nodiscard]] TextWithEntities ErrorText(const Webview::Available &info);
|
||||
|
||||
struct MainButtonArgs {
|
||||
bool isActive = false;
|
||||
bool isVisible = false;
|
||||
bool isProgressVisible = false;
|
||||
QString text;
|
||||
};
|
||||
|
||||
enum class MenuButton {
|
||||
None = 0x00,
|
||||
OpenBot = 0x01,
|
||||
@@ -126,10 +121,13 @@ private:
|
||||
void setTitle(rpl::producer<QString> title);
|
||||
void sendDataMessage(const QJsonObject &args);
|
||||
void switchInlineQueryMessage(const QJsonObject &args);
|
||||
void processMainButtonMessage(const QJsonObject &args);
|
||||
void processButtonMessage(
|
||||
std::unique_ptr<Button> &button,
|
||||
const QJsonObject &args);
|
||||
void processBackButtonMessage(const QJsonObject &args);
|
||||
void processSettingsButtonMessage(const QJsonObject &args);
|
||||
void processHeaderColor(const QJsonObject &args);
|
||||
void processBottomBarColor(const QJsonObject &args);
|
||||
void openTgLink(const QJsonObject &args);
|
||||
void openExternalLink(const QJsonObject &args);
|
||||
void openInvoice(const QJsonObject &args);
|
||||
@@ -144,7 +142,7 @@ private:
|
||||
void replyCustomMethod(QJsonValue requestId, QJsonObject response);
|
||||
void requestClipboardText(const QJsonObject &args);
|
||||
void setupClosingBehaviour(const QJsonObject &args);
|
||||
void createMainButton();
|
||||
void createButton(std::unique_ptr<Button> &button);
|
||||
void scheduleCloseWithConfirmation();
|
||||
void closeWithConfirmation();
|
||||
void sendViewport();
|
||||
@@ -158,7 +156,7 @@ private:
|
||||
[[nodiscard]] bool progressWithBackground() const;
|
||||
[[nodiscard]] QRect progressRect() const;
|
||||
void setupProgressGeometry();
|
||||
void updateFooterHeight();
|
||||
void layoutButtons();
|
||||
|
||||
Webview::StorageId _storageId;
|
||||
const not_null<Delegate*> _delegate;
|
||||
@@ -170,14 +168,16 @@ private:
|
||||
std::unique_ptr<RpWidget> _webviewBottom;
|
||||
rpl::variable<QString> _bottomText;
|
||||
QPointer<RpWidget> _webviewParent;
|
||||
std::unique_ptr<RpWidget> _bottomButtonsBg;
|
||||
std::unique_ptr<Button> _mainButton;
|
||||
mutable crl::time _mainButtonLastClick = 0;
|
||||
std::unique_ptr<Button> _secondaryButton;
|
||||
RectPart _secondaryPosition = RectPart::Left;
|
||||
rpl::variable<int> _footerHeight = 0;
|
||||
std::unique_ptr<Progress> _progress;
|
||||
rpl::event_stream<> _themeUpdateForced;
|
||||
std::optional<QColor> _bottomBarColor;
|
||||
rpl::lifetime _headerColorLifetime;
|
||||
rpl::lifetime _fgLifetime;
|
||||
rpl::lifetime _bgLifetime;
|
||||
rpl::lifetime _bottomBarColorLifetime;
|
||||
bool _webviewProgress = false;
|
||||
bool _themeUpdateScheduled = false;
|
||||
bool _hiddenForPayment = false;
|
||||
|
||||
@@ -1009,6 +1009,7 @@ chatGiveawayEndDateMargin: margins(11px, 0px, 11px, 16px);
|
||||
chatGiveawayPeerSize: 32px;
|
||||
chatGiveawayPeerPadding: margins(5px, 7px, 12px, 0px);
|
||||
chatGiveawayPeerSkip: 8px;
|
||||
chatGiveawayCreditsIconHeight: 19px;
|
||||
|
||||
chatSimilarRadius: 12px;
|
||||
chatSimilarArrowSize: 6px;
|
||||
|
||||
@@ -105,11 +105,22 @@ private:
|
||||
icon->paint(p, iconLeft, myIconTop, size);
|
||||
|
||||
const auto paintName = [&](const QString &text, int top) {
|
||||
const auto &font = st.style.font;
|
||||
p.drawText(
|
||||
QRect(0, top, column, font->height),
|
||||
font->elided(text, available),
|
||||
style::al_top);
|
||||
auto string = Ui::Text::String(
|
||||
st.style,
|
||||
text,
|
||||
kDefaultTextOptions,
|
||||
available);
|
||||
string.draw(p, {
|
||||
.position = QPoint(
|
||||
std::max(
|
||||
(column - string.maxWidth()) / 2,
|
||||
skip),
|
||||
top),
|
||||
.outerWidth = available,
|
||||
.availableWidth = available,
|
||||
.align = style::al_left,
|
||||
.elisionLines = 1,
|
||||
});
|
||||
};
|
||||
p.setFont(st.style.font);
|
||||
p.setPen(st.textFg);
|
||||
@@ -456,4 +467,4 @@ object_ptr<RoundButton> FilterLinkProcessButton(
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
} // namespace Ui
|
||||
|
||||
@@ -84,13 +84,7 @@ PaintRoundImageCallback MultiThumbnail(
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QImage GenerateStars(int height, int count) {
|
||||
constexpr auto kOutlineWidth = .6;
|
||||
constexpr auto kStrokeWidth = 3;
|
||||
constexpr auto kShift = 3;
|
||||
|
||||
QByteArray CreditsIconSvg(int strokeWidth) {
|
||||
auto colorized = qs(Premium::ColorizedSvg(
|
||||
Premium::CreditsIconGradientStops()));
|
||||
colorized.replace(
|
||||
@@ -98,8 +92,18 @@ QImage GenerateStars(int height, int count) {
|
||||
u"stroke=\"%1\""_q.arg(st::creditsStroke->c.name()));
|
||||
colorized.replace(
|
||||
u"stroke-width=\"1\""_q,
|
||||
u"stroke-width=\"%1\""_q.arg(kStrokeWidth));
|
||||
auto svg = QSvgRenderer(colorized.toUtf8());
|
||||
u"stroke-width=\"%1\""_q.arg(strokeWidth));
|
||||
return colorized.toUtf8();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QImage GenerateStars(int height, int count) {
|
||||
constexpr auto kOutlineWidth = .6;
|
||||
constexpr auto kStrokeWidth = 3;
|
||||
constexpr auto kShift = 3;
|
||||
|
||||
auto svg = QSvgRenderer(CreditsIconSvg(kStrokeWidth));
|
||||
svg.setViewBox(svg.viewBox() + Margins(kStrokeWidth));
|
||||
|
||||
const auto starSize = Size(height - kOutlineWidth * 2);
|
||||
@@ -457,6 +461,8 @@ Fn<PaintRoundImageCallback(Fn<void()>)> PaintPreviewCallback(
|
||||
TextWithEntities GenerateEntryName(const Data::CreditsHistoryEntry &entry) {
|
||||
return (entry.reaction
|
||||
? tr::lng_credits_box_history_entry_reaction_name
|
||||
: entry.bareGiveawayMsgId
|
||||
? tr::lng_credits_box_history_entry_giveaway_name
|
||||
: entry.gift
|
||||
? tr::lng_credits_box_history_entry_gift_name
|
||||
: (entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment)
|
||||
@@ -470,4 +476,93 @@ TextWithEntities GenerateEntryName(const Data::CreditsHistoryEntry &entry) {
|
||||
TextWithEntities::Simple);
|
||||
}
|
||||
|
||||
Fn<void(QPainter &)> PaintOutlinedColoredCreditsIconCallback(
|
||||
int size,
|
||||
float64 outlineRatio) {
|
||||
// constexpr auto kIdealSize = 42;
|
||||
constexpr auto kPoints = uint(16);
|
||||
constexpr auto kAngleStep = 2. * M_PI / kPoints;
|
||||
constexpr auto kOutlineWidth = 1.6;
|
||||
// constexpr auto kStarShift = 3.8;
|
||||
constexpr auto kStrokeWidth = 3;
|
||||
const auto starSize = Size(size);
|
||||
|
||||
auto svg = std::make_shared<QSvgRenderer>(CreditsIconSvg(kStrokeWidth));
|
||||
svg->setViewBox(svg->viewBox() + Margins(kStrokeWidth));
|
||||
const auto s = style::ConvertFloatScale(kOutlineWidth * outlineRatio);
|
||||
return [=](QPainter &q) {
|
||||
q.save();
|
||||
q.setCompositionMode(QPainter::CompositionMode_Clear);
|
||||
for (auto i = 0; i < kPoints; ++i) {
|
||||
const auto angle = i * kAngleStep;
|
||||
const auto x = s * std::cos(angle);
|
||||
const auto y = s * std::sin(angle);
|
||||
svg->render(&q, QRectF(QPointF(x, y), starSize));
|
||||
}
|
||||
q.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
svg->render(&q, Rect(starSize));
|
||||
q.restore();
|
||||
};
|
||||
}
|
||||
|
||||
QImage CreditsWhiteDoubledIcon(int size, float64 outlineRatio) {
|
||||
auto svg = QSvgRenderer(Ui::Premium::Svg());
|
||||
auto result = QImage(
|
||||
Size(size) * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
result.fill(Qt::transparent);
|
||||
result.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
|
||||
// constexpr auto kIdealSize = 42;
|
||||
constexpr auto kPoints = uint(16);
|
||||
constexpr auto kAngleStep = 2. * M_PI / kPoints;
|
||||
constexpr auto kOutlineWidth = 1.6;
|
||||
constexpr auto kStarShift = 3.8;
|
||||
const auto userpicRect = Rect(Size(size));
|
||||
const auto starRect = userpicRect - Margins(userpicRect.width() / 4.);
|
||||
const auto starSize = starRect.size();
|
||||
const auto drawSingle = [&](QPainter &q) {
|
||||
const auto s = style::ConvertFloatScale(kOutlineWidth * outlineRatio);
|
||||
q.save();
|
||||
q.setCompositionMode(QPainter::CompositionMode_Clear);
|
||||
for (auto i = 0; i < kPoints; ++i) {
|
||||
const auto angle = i * kAngleStep;
|
||||
const auto x = s * std::cos(angle);
|
||||
const auto y = s * std::sin(angle);
|
||||
svg.render(&q, QRectF(QPointF(x, y), starSize));
|
||||
}
|
||||
q.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
svg.render(&q, Rect(starSize));
|
||||
q.restore();
|
||||
};
|
||||
{
|
||||
auto p = QPainter(&result);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::lightButtonFg);
|
||||
p.translate(starRect.topLeft());
|
||||
p.translate(
|
||||
style::ConvertFloatScale(kStarShift * outlineRatio) / 2.,
|
||||
0);
|
||||
drawSingle(p);
|
||||
{
|
||||
// Remove the previous star at bottom.
|
||||
p.setCompositionMode(QPainter::CompositionMode_Clear);
|
||||
p.save();
|
||||
p.resetTransform();
|
||||
p.fillRect(
|
||||
userpicRect.x(),
|
||||
userpicRect.y(),
|
||||
userpicRect.width() / 2.,
|
||||
userpicRect.height(),
|
||||
Qt::transparent);
|
||||
p.restore();
|
||||
}
|
||||
p.translate(
|
||||
-style::ConvertFloatScale(kStarShift * outlineRatio),
|
||||
0);
|
||||
drawSingle(p);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -20,12 +20,10 @@ class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class MaskedInputField;
|
||||
class RpWidget;
|
||||
class VerticalLayout;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui {
|
||||
|
||||
using PaintRoundImageCallback = Fn<void(
|
||||
Painter &p,
|
||||
@@ -73,4 +71,10 @@ Fn<PaintRoundImageCallback(Fn<void()>)> PaintPreviewCallback(
|
||||
[[nodiscard]] TextWithEntities GenerateEntryName(
|
||||
const Data::CreditsHistoryEntry &entry);
|
||||
|
||||
Fn<void(QPainter &)> PaintOutlinedColoredCreditsIconCallback(
|
||||
int size,
|
||||
float64 outlineRatio);
|
||||
|
||||
[[nodiscard]] QImage CreditsWhiteDoubledIcon(int size, float64 outlineRatio);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -1503,6 +1503,7 @@ bool ReadPaletteValues(const QByteArray &content, Fn<bool(QLatin1String name, QL
|
||||
{ "section_header_text_color", st::windowActiveTextFg },
|
||||
{ "subtitle_text_color", st::windowSubTextFg },
|
||||
{ "destructive_text_color", st::attentionButtonFg },
|
||||
{ "bottom_bar_bg_color", st::windowBg },
|
||||
};
|
||||
auto object = QJsonObject();
|
||||
const auto wrap = [](QColor color) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
AppVersion 5004006
|
||||
AppVersionStrMajor 5.4
|
||||
AppVersionStrSmall 5.4.6
|
||||
AppVersionStr 5.4.6
|
||||
BetaChannel 1
|
||||
AppVersion 5005002
|
||||
AppVersionStrMajor 5.5
|
||||
AppVersionStrSmall 5.5.2
|
||||
AppVersionStr 5.5.2
|
||||
BetaChannel 0
|
||||
AlphaVersion 0
|
||||
AppVersionOriginal 5.4.6.beta
|
||||
AppVersionOriginal 5.5.2
|
||||
|
||||
Submodule Telegram/lib_ui updated: 9d2c1836eb...eaf4437e49
Submodule Telegram/lib_webview updated: f3de8aa1a9...2de655f58d
@@ -1,3 +1,20 @@
|
||||
5.5.2 (09.09.24)
|
||||
|
||||
- Support two bottom buttons in web apps.
|
||||
- Fix text layout outside of the message bubble glitch.
|
||||
- Don't stop video when dragging media viewer in window mode.
|
||||
|
||||
5.5.1 (06.09.24)
|
||||
|
||||
- Fix crash in peer short info box.
|
||||
|
||||
5.5 (06.09.24)
|
||||
|
||||
- Star Giveaways.
|
||||
- Swipe-to-Reply.
|
||||
- Audio device selection in one-on-one calls.
|
||||
- Text selection in collapsed / expanded quotes.
|
||||
|
||||
5.4.6 beta (04.09.24)
|
||||
|
||||
- Add Swipe-To-Reply for modern touchpads and touchscreens.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user