Merge tag 'v5.8.3' into dev

This commit is contained in:
AlexeyZavar 2024-11-24 12:06:52 +03:00
commit 9db8cc6707
54 changed files with 1553 additions and 521 deletions

View file

@ -374,6 +374,8 @@ PRIVATE
boxes/peer_list_box.h boxes/peer_list_box.h
boxes/peer_list_controllers.cpp boxes/peer_list_controllers.cpp
boxes/peer_list_controllers.h boxes/peer_list_controllers.h
boxes/peer_list_widgets.cpp
boxes/peer_list_widgets.h
boxes/peer_lists_box.cpp boxes/peer_lists_box.cpp
boxes/peer_lists_box.h boxes/peer_lists_box.h
boxes/passcode_box.cpp boxes/passcode_box.cpp
@ -1631,6 +1633,8 @@ PRIVATE
ui/widgets/label_with_custom_emoji.h ui/widgets/label_with_custom_emoji.h
ui/widgets/chat_filters_tabs_strip.cpp ui/widgets/chat_filters_tabs_strip.cpp
ui/widgets/chat_filters_tabs_strip.h ui/widgets/chat_filters_tabs_strip.h
ui/widgets/peer_bubble.cpp
ui/widgets/peer_bubble.h
ui/countryinput.cpp ui/countryinput.cpp
ui/countryinput.h ui/countryinput.h
ui/dynamic_thumbnails.cpp ui/dynamic_thumbnails.cpp

View file

@ -290,6 +290,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_error_cant_add_admin_invite" = "You can't add this user as an admin because they are not a member of this group and you are not allowed to add them."; "lng_error_cant_add_admin_invite" = "You can't add this user as an admin because they are not a member of this group and you are not allowed to add them.";
"lng_error_cant_add_admin_unban" = "Sorry, you can't add this user as an admin because they are in the Removed Users list and you can't unban them."; "lng_error_cant_add_admin_unban" = "Sorry, you can't add this user as an admin because they are in the Removed Users list and you can't unban them.";
"lng_error_cant_ban_admin" = "You can't ban this user because they are an admin in this group and you are not allowed to demote them."; "lng_error_cant_ban_admin" = "You can't ban this user because they are an admin in this group and you are not allowed to demote them.";
"lng_error_cant_reply_other" = "This message can't be replied in another chat.";
"lng_error_admin_limit" = "Sorry, you've reached the maximum number of admins for this group."; "lng_error_admin_limit" = "Sorry, you've reached the maximum number of admins for this group.";
"lng_error_admin_limit_channel" = "Sorry, you've reached the maximum number of admins for this channel."; "lng_error_admin_limit_channel" = "Sorry, you've reached the maximum number of admins for this channel.";
"lng_error_post_link_invalid" = "Unfortunately, you can't access this message. You aren't a member of the chat where it was posted."; "lng_error_post_link_invalid" = "Unfortunately, you can't access this message. You aren't a member of the chat where it was posted.";
@ -298,6 +299,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_error_nocopy_group" = "Sorry, copying from this group is disabled by admins."; "lng_error_nocopy_group" = "Sorry, copying from this group is disabled by admins.";
"lng_error_nocopy_channel" = "Sorry, copying from this channel is disabled by admins."; "lng_error_nocopy_channel" = "Sorry, copying from this channel is disabled by admins.";
"lng_error_nocopy_story" = "Sorry, the creator of this story disabled copying."; "lng_error_nocopy_story" = "Sorry, the creator of this story disabled copying.";
"lng_error_schedule_limit" = "Sorry, you can't schedule more than 100 messages.";
"lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?"; "lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?";
"lng_sure_add_admin_invite_channel" = "This user is not a subscriber of this channel. Add them to the channel and promote them to admin?"; "lng_sure_add_admin_invite_channel" = "This user is not a subscriber of this channel. Add them to the channel and promote them to admin?";
"lng_sure_add_admin_unremove" = "This user is currently restricted or removed. Are you sure you want to promote them?"; "lng_sure_add_admin_unremove" = "This user is currently restricted or removed. Are you sure you want to promote them?";
@ -1861,6 +1863,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_payment_init_recurring_for" = "You successfully transferred {amount} to {user} for {invoice} and allowed future recurring payments"; "lng_action_payment_init_recurring_for" = "You successfully transferred {amount} to {user} for {invoice} and allowed future recurring payments";
"lng_action_payment_init_recurring" = "You successfully transferred {amount} to {user} and allowed future recurring payments"; "lng_action_payment_init_recurring" = "You successfully transferred {amount} to {user} and allowed future recurring payments";
"lng_action_payment_used_recurring" = "You were charged {amount} via recurring payment"; "lng_action_payment_used_recurring" = "You were charged {amount} via recurring payment";
"lng_action_payment_bot_done" = "Bot connected to this account received {amount}";
"lng_action_payment_bot_recurring" = "Bot connected to this account received {amount} via recurring payment";
"lng_action_took_screenshot" = "{from} took a screenshot!"; "lng_action_took_screenshot" = "{from} took a screenshot!";
"lng_action_you_took_screenshot" = "You took a screenshot!"; "lng_action_you_took_screenshot" = "You took a screenshot!";
"lng_action_bot_allowed_from_domain" = "You allowed this bot to message you when you logged in on {domain}."; "lng_action_bot_allowed_from_domain" = "You allowed this bot to message you when you logged in on {domain}.";
@ -2444,6 +2448,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_out_media#other" = "Do you want to unlock {media} in {chat} for **{count} Stars**?"; "lng_credits_box_out_media#other" = "Do you want to unlock {media} in {chat} for **{count} Stars**?";
"lng_credits_box_out_media_user#one" = "Do you want to unlock {media} from {user} for **{count} Star**?"; "lng_credits_box_out_media_user#one" = "Do you want to unlock {media} from {user} for **{count} Star**?";
"lng_credits_box_out_media_user#other" = "Do you want to unlock {media} from {user} for **{count} Stars**?"; "lng_credits_box_out_media_user#other" = "Do you want to unlock {media} from {user} for **{count} Stars**?";
"lng_credits_box_out_subscription_bot#one" = "Do you want to subscribe to **{title}** in **{recipient}** for **{count}** star per month?";
"lng_credits_box_out_subscription_bot#other" = "Do you want to subscribe to **{title}** in **{recipient}** for **{count}** stars per month?";
"lng_credits_box_out_subscription_business#one" = "Do you want to subscribe to **{title}** from **{recipient}** for **{count}** star per month?";
"lng_credits_box_out_subscription_business#other" = "Do you want to subscribe to **{title}** from **{recipient}** for **{count}** stars per month?";
"lng_credits_box_out_subscription_confirm#one" = "Subscribe for {emoji} {count} / month";
"lng_credits_box_out_subscription_confirm#other" = "Subscribe for {emoji} {count} / month";
"lng_credits_box_out_photo" = "a photo"; "lng_credits_box_out_photo" = "a photo";
"lng_credits_box_out_photos#one" = "{count} photo"; "lng_credits_box_out_photos#one" = "{count} photo";
"lng_credits_box_out_photos#other" = "{count} photos"; "lng_credits_box_out_photos#other" = "{count} photos";
@ -2504,6 +2514,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_subscriber_subtitle" = "appx. {total} per month"; "lng_credits_subscriber_subtitle" = "appx. {total} per month";
"lng_credits_subscription_row_to" = "Subscription"; "lng_credits_subscription_row_to" = "Subscription";
"lng_credits_subscription_row_to_bot" = "Bot";
"lng_credits_subscription_row_to_business" = "Business";
"lng_credits_subscription_row_from" = "Subscribed"; "lng_credits_subscription_row_from" = "Subscribed";
"lng_credits_subscription_row_next_on" = "Renews"; "lng_credits_subscription_row_next_on" = "Renews";
@ -2514,13 +2526,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_subscription_on_about" = "If you cancel now, you will still be able to access your subscription until {date}."; "lng_credits_subscription_on_about" = "If you cancel now, you will still be able to access your subscription until {date}.";
"lng_credits_subscription_off_button" = "Renew Subscription"; "lng_credits_subscription_off_button" = "Renew Subscription";
"lng_credits_subscription_off_rejoin_button" = "Subscribe again";
"lng_credits_subscription_off_about" = "You have canceled your subscription."; "lng_credits_subscription_off_about" = "You have canceled your subscription.";
"lng_credits_subscription_off_by_bot_about" = "{bot} has canceled your subscription.";
"lng_credits_subscription_status_on" = "renews on {date}"; "lng_credits_subscription_status_on" = "renews on {date}";
"lng_credits_subscription_status_off" = "expires on {date}"; "lng_credits_subscription_status_off" = "expires on {date}";
"lng_credits_subscription_status_none" = "expired on {date}"; "lng_credits_subscription_status_none" = "expired on {date}";
"lng_credits_subscription_status_off_right" = "canceled"; "lng_credits_subscription_status_off_right" = "canceled";
"lng_credits_subscription_status_none_right" = "expired"; "lng_credits_subscription_status_none_right" = "expired";
"lng_credits_subscription_status_off_by_bot_right" = "canceled\nby bot";
"lng_credits_small_balance_title#one" = "{count} Star Needed"; "lng_credits_small_balance_title#one" = "{count} Star Needed";
"lng_credits_small_balance_title#other" = "{count} Stars Needed"; "lng_credits_small_balance_title#other" = "{count} Stars Needed";

View file

@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop" <Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE" ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="5.8.2.0" /> Version="5.8.3.0" />
<Properties> <Properties>
<DisplayName>Telegram Desktop</DisplayName> <DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,8,2,0 FILEVERSION 5,8,3,0
PRODUCTVERSION 5,8,2,0 PRODUCTVERSION 5,8,3,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -62,10 +62,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop" VALUE "FileDescription", "AyuGram Desktop"
VALUE "FileVersion", "5.8.2.0" VALUE "FileVersion", "5.8.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.8.2.0" VALUE "ProductVersion", "5.8.3.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,8,2,0 FILEVERSION 5,8,3,0
PRODUCTVERSION 5,8,2,0 PRODUCTVERSION 5,8,3,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -53,10 +53,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater" VALUE "FileDescription", "AyuGram Desktop Updater"
VALUE "FileVersion", "5.8.2.0" VALUE "FileVersion", "5.8.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.8.2.0" VALUE "ProductVersion", "5.8.3.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_chat_invite.h" #include "api/api_chat_invite.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "api/api_credits.h"
#include "boxes/premium_limits_box.h" #include "boxes/premium_limits_box.h"
#include "core/application.h" #include "core/application.h"
#include "data/components/credits.h" #include "data/components/credits.h"
@ -295,20 +296,39 @@ void ConfirmSubscriptionBox(
const auto buttonWidth = state->saveButton const auto buttonWidth = state->saveButton
? state->saveButton->width() ? state->saveButton->width()
: 0; : 0;
const auto finish = [=] {
state->api = std::nullopt;
state->loading.force_assign(false);
if (const auto strong = weak.data()) {
strong->closeBox();
}
};
state->api->request( state->api->request(
MTPpayments_SendStarsForm( MTPpayments_SendStarsForm(
MTP_long(formId), MTP_long(formId),
MTP_inputInvoiceChatInviteSubscription(MTP_string(hash))) MTP_inputInvoiceChatInviteSubscription(MTP_string(hash)))
).done([=](const MTPpayments_PaymentResult &result) { ).done([=](const MTPpayments_PaymentResult &result) {
state->api = std::nullopt;
state->loading.force_assign(false);
result.match([&](const MTPDpayments_paymentResult &data) { result.match([&](const MTPDpayments_paymentResult &data) {
session->api().applyUpdates(data.vupdates()); session->api().applyUpdates(data.vupdates());
}, [](const MTPDpayments_paymentVerificationNeeded &data) { }, [](const MTPDpayments_paymentVerificationNeeded &data) {
}); });
if (weak) { const auto refill = session->data().activeCreditsSubsRebuilder();
box->closeBox(); const auto strong = weak.data();
if (!strong) {
return;
} }
if (!refill) {
return finish();
}
const auto api
= strong->lifetime().make_state<Api::CreditsHistory>(
session->user(),
true,
true);
api->requestSubscriptions({}, [=](Data::CreditsStatusSlice d) {
refill->fire(std::move(d));
finish();
});
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
const auto id = error.type(); const auto id = error.type();
if (weak) { if (weak) {

View file

@ -133,17 +133,26 @@ constexpr auto kTransactionsLimit = 100;
} }
[[nodiscard]] Data::SubscriptionEntry SubscriptionFromTL( [[nodiscard]] Data::SubscriptionEntry SubscriptionFromTL(
const MTPStarsSubscription &tl) { const MTPStarsSubscription &tl,
not_null<PeerData*> peer) {
return Data::SubscriptionEntry{ return Data::SubscriptionEntry{
.id = qs(tl.data().vid()), .id = qs(tl.data().vid()),
.inviteHash = qs(tl.data().vchat_invite_hash().value_or_empty()), .inviteHash = qs(tl.data().vchat_invite_hash().value_or_empty()),
.title = qs(tl.data().vtitle().value_or_empty()),
.slug = qs(tl.data().vinvoice_slug().value_or_empty()),
.until = base::unixtime::parse(tl.data().vuntil_date().v), .until = base::unixtime::parse(tl.data().vuntil_date().v),
.subscription = Data::PeerSubscription{ .subscription = Data::PeerSubscription{
.credits = tl.data().vpricing().data().vamount().v, .credits = tl.data().vpricing().data().vamount().v,
.period = tl.data().vpricing().data().vperiod().v, .period = tl.data().vpricing().data().vperiod().v,
}, },
.barePeerId = peerFromMTP(tl.data().vpeer()).value, .barePeerId = peerFromMTP(tl.data().vpeer()).value,
.photoId = (tl.data().vphoto()
? peer->owner().photoFromWeb(
*tl.data().vphoto(),
ImageLocation())->id
: 0),
.cancelled = tl.data().is_canceled(), .cancelled = tl.data().is_canceled(),
.cancelledByBot = tl.data().is_bot_canceled(),
.expired = (base::unixtime::now() > tl.data().vuntil_date().v), .expired = (base::unixtime::now() > tl.data().vuntil_date().v),
.canRefulfill = tl.data().is_can_refulfill(), .canRefulfill = tl.data().is_can_refulfill(),
}; };
@ -166,7 +175,7 @@ constexpr auto kTransactionsLimit = 100;
if (const auto history = data.vsubscriptions()) { if (const auto history = data.vsubscriptions()) {
subscriptions.reserve(history->v.size()); subscriptions.reserve(history->v.size());
for (const auto &tl : history->v) { for (const auto &tl : history->v) {
subscriptions.push_back(SubscriptionFromTL(tl)); subscriptions.push_back(SubscriptionFromTL(tl, peer));
} }
} }
return Data::CreditsStatusSlice{ return Data::CreditsStatusSlice{
@ -463,4 +472,20 @@ Data::CreditsGiveawayOptions CreditsGiveawayOptions::options() const {
return _options; return _options;
} }
void EditCreditsSubscription(
not_null<Main::Session*> session,
const QString &id,
bool cancel,
Fn<void()> done,
Fn<void(QString)> fail) {
using Flag = MTPpayments_ChangeStarsSubscription::Flag;
session->api().request(
MTPpayments_ChangeStarsSubscription(
MTP_flags(Flag::f_canceled),
MTP_inputPeerSelf(),
MTP_string(id),
MTP_bool(cancel)
)).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send();
}
} // namespace Api } // namespace Api

View file

@ -109,4 +109,11 @@ private:
[[nodiscard]] rpl::producer<not_null<PeerData*>> PremiumPeerBot( [[nodiscard]] rpl::producer<not_null<PeerData*>> PremiumPeerBot(
not_null<Main::Session*> session); not_null<Main::Session*> session);
void EditCreditsSubscription(
not_null<Main::Session*> session,
const QString &id,
bool cancel,
Fn<void()> done,
Fn<void(QString)> fail);
} // namespace Api } // namespace Api

View file

@ -582,6 +582,14 @@ void ApiWrap::sendMessageFail(
: tr::lng_error_noforwards_group(tr::now), kJoinErrorDuration); : tr::lng_error_noforwards_group(tr::now), kJoinErrorDuration);
} else if (error == u"PREMIUM_ACCOUNT_REQUIRED"_q) { } else if (error == u"PREMIUM_ACCOUNT_REQUIRED"_q) {
Settings::ShowPremium(&session(), "premium_stickers"); Settings::ShowPremium(&session(), "premium_stickers");
} else if (error == u"SCHEDULE_TOO_MUCH"_q) {
auto &scheduled = _session->scheduledMessages();
if (const auto item = scheduled.lookupItem(peer->id, itemId.msg)) {
scheduled.removeSending(item);
}
if (show) {
show->showToast(tr::lng_error_schedule_limit(tr::now));
}
} }
if (const auto item = _session->data().message(itemId)) { if (const auto item = _session->data().message(itemId)) {
Assert(randomId != 0); Assert(randomId != 0);

View file

@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "boxes/filters/edit_filter_chats_list.h" #include "boxes/filters/edit_filter_chats_list.h"
#include "data/data_chat_filters.h"
#include "data/data_premium_limits.h" #include "data/data_premium_limits.h"
#include "data/data_session.h"
#include "history/history.h" #include "history/history.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
@ -125,7 +127,15 @@ Flag TypeRow::flag() const {
} }
ExceptionRow::ExceptionRow(not_null<History*> history) : Row(history) { ExceptionRow::ExceptionRow(not_null<History*> history) : Row(history) {
if (peer()->isSelf()) { auto filters = QStringList();
for (const auto &filter : history->owner().chatsFilters().list()) {
if (filter.contains(history) && filter.id()) {
filters << filter.title();
}
}
if (!filters.isEmpty()) {
setCustomStatus(filters.join(", "));
} else if (peer()->isSelf()) {
setCustomStatus(tr::lng_saved_forward_here(tr::now)); setCustomStatus(tr::lng_saved_forward_here(tr::now));
} }
} }

View file

@ -1247,25 +1247,20 @@ void AddCreditsHistoryEntryTable(
})); }));
} }
} }
if (!entry.subscriptionUntil.isNull() && !entry.title.isEmpty()) {
AddTableRow(
table,
tr::lng_gift_link_label_reason(),
tr::lng_credits_box_history_entry_subscription(
Ui::Text::WithEntities));
}
if (!entry.id.isEmpty()) { if (!entry.id.isEmpty()) {
constexpr auto kOneLineCount = 22; constexpr auto kOneLineCount = 24;
const auto oneLine = entry.id.size() <= kOneLineCount; const auto oneLine = entry.id.length() <= kOneLineCount;
auto multiLine = QString();
if (!oneLine) {
for (auto i = 0; i < entry.id.size(); ++i) {
multiLine.append(entry.id[i]);
if ((i + 1) % kOneLineCount == 0) {
multiLine.append('\n');
}
}
}
auto label = object_ptr<Ui::FlatLabel>( auto label = object_ptr<Ui::FlatLabel>(
table, table,
rpl::single( rpl::single(
Ui::Text::Wrapped( Ui::Text::Wrapped({ entry.id }, EntityType::Code, {})),
{ oneLine ? entry.id : std::move(multiLine) },
EntityType::Code,
{})),
oneLine oneLine
? st::giveawayGiftCodeValue ? st::giveawayGiftCodeValue
: st::giveawayGiftCodeValueMultiline); : st::giveawayGiftCodeValueMultiline);
@ -1324,11 +1319,24 @@ void AddSubscriptionEntryTable(
st::giveawayGiftCodeTable), st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin); st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(s.barePeerId); const auto peerId = PeerId(s.barePeerId);
const auto user = peerIsUser(peerId)
? controller->session().data().peer(peerId)->asUser()
: nullptr;
AddTableRow( AddTableRow(
table, table,
tr::lng_credits_subscription_row_to(), (!s.title.isEmpty() && user && user->botInfo)
? tr::lng_credits_subscription_row_to_bot()
: (!s.title.isEmpty() && user && !user->botInfo)
? tr::lng_credits_subscription_row_to_business()
: tr::lng_credits_subscription_row_to(),
controller, controller,
peerId); peerId);
if (!s.title.isEmpty()) {
AddTableRow(
table,
tr::lng_credits_subscription_row_to(),
rpl::single(Ui::Text::WithEntities(s.title)));
}
if (!s.until.isNull()) { if (!s.until.isNull()) {
if (s.subscription.period > 0) { if (s.subscription.period > 0) {
const auto subscribed = s.until.addSecs(-s.subscription.period); const auto subscribed = s.until.addSecs(-s.subscription.period);

View file

@ -0,0 +1,388 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peer_list_widgets.h"
#include "ui/painter.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/vertical_layout.h"
#include "styles/style_boxes.h"
namespace {
using State = std::unique_ptr<PeerListState>;
} // namespace
PeerListWidgets::PeerListWidgets(
not_null<Ui::RpWidget*> parent,
not_null<PeerListController*> controller)
: Ui::RpWidget(parent)
, _controller(controller)
, _st(controller->computeListSt()) {
_content = base::make_unique_q<Ui::VerticalLayout>(this);
parent->sizeValue() | rpl::start_with_next([this](const QSize &size) {
_content->resizeToWidth(size.width());
resize(size.width(), _content->height());
}, lifetime());
}
crl::time PeerListWidgets::paintRow(
Painter &p,
crl::time now,
bool selected,
not_null<PeerListRow*> row) {
const auto &st = row->computeSt(_st.item);
row->lazyInitialize(st);
const auto outerWidth = _content->width();
const auto w = outerWidth;
auto refreshStatusAt = row->refreshStatusTime();
if (refreshStatusAt > 0 && now >= refreshStatusAt) {
row->refreshStatus();
refreshStatusAt = row->refreshStatusTime();
}
const auto refreshStatusIn = (refreshStatusAt > 0)
? std::max(refreshStatusAt - now, crl::time(1))
: 0;
row->paintUserpic(
p,
st,
st.photoPosition.x(),
st.photoPosition.y(),
outerWidth);
p.setPen(st::contactsNameFg);
const auto skipRight = st.photoPosition.x();
const auto rightActionSize = row->rightActionSize();
const auto rightActionMargins = rightActionSize.isEmpty()
? QMargins()
: row->rightActionMargins();
const auto &name = row->name();
const auto namePosition = st.namePosition;
const auto namex = namePosition.x();
const auto namey = namePosition.y();
auto namew = outerWidth - namex - skipRight;
if (!rightActionSize.isEmpty()
&& (namey < rightActionMargins.top() + rightActionSize.height())
&& (namey + st.nameStyle.font->height
> rightActionMargins.top())) {
namew -= rightActionMargins.left()
+ rightActionSize.width()
+ rightActionMargins.right()
- skipRight;
}
const auto statusx = st.statusPosition.x();
const auto statusy = st.statusPosition.y();
auto statusw = outerWidth - statusx - skipRight;
if (!rightActionSize.isEmpty()
&& (statusy < rightActionMargins.top() + rightActionSize.height())
&& (statusy + st::contactsStatusFont->height
> rightActionMargins.top())) {
statusw -= rightActionMargins.left()
+ rightActionSize.width()
+ rightActionMargins.right()
- skipRight;
}
namew -= row->paintNameIconGetWidth(
p,
[=] { updateRow(row); },
now,
namex,
namey,
name.maxWidth(),
namew,
w,
selected);
auto nameCheckedRatio = row->disabled() ? 0. : row->checkedRatio();
p.setPen(anim::pen(st.nameFg, st.nameFgChecked, nameCheckedRatio));
name.drawLeftElided(p, namex, namey, namew, w);
p.setFont(st::contactsStatusFont);
row->paintStatusText(p, st, statusx, statusy, statusw, w, selected);
row->elementsPaint(p, outerWidth, selected, 0);
return refreshStatusIn;
}
void PeerListWidgets::appendRow(std::unique_ptr<PeerListRow> row) {
Expects(row != nullptr);
if (_rowsById.find(row->id()) == _rowsById.cend()) {
const auto raw = row.get();
const auto &st = raw->computeSt(_st.item);
raw->setAbsoluteIndex(_rows.size());
_rows.push_back(std::move(row));
const auto widget = _content->add(
object_ptr<Ui::AbstractButton>::fromRaw(
Ui::CreateSimpleSettingsButton(
_content.get(),
st.button.ripple,
st.button.textBgOver)));
widget->resize(widget->width(), st.height);
widget->paintRequest() | rpl::start_with_next([=, this] {
auto p = Painter(widget);
const auto selected = widget->isOver() || widget->isDown();
paintRow(p, crl::now(), selected, raw);
}, widget->lifetime());
widget->setClickedCallback([this, raw] {
_controller->rowClicked(raw);
});
}
}
PeerListRow *PeerListWidgets::findRow(PeerListRowId id) {
const auto it = _rowsById.find(id);
return (it == _rowsById.cend()) ? nullptr : it->second.get();
}
void PeerListWidgets::updateRow(not_null<PeerListRow*> row) {
const auto it = ranges::find_if(
_rows,
[row](const auto &r) { return r.get() == row; });
if (it != _rows.end()) {
const auto index = std::distance(_rows.begin(), it);
if (const auto widget = _content->widgetAt(index)) {
widget->update();
}
}
}
int PeerListWidgets::fullRowsCount() {
return _rows.size();
}
[[nodiscard]] not_null<PeerListRow*> PeerListWidgets::rowAt(int index) {
Expects(index >= 0 && index < _rows.size());
return _rows[index].get();
}
void PeerListWidgets::refreshRows() {
_content->resizeToWidth(width());
resize(width(), _content->height());
}
void PeerListWidgetsDelegate::setContent(PeerListWidgets *content) {
_content = content;
}
void PeerListWidgetsDelegate::setUiShow(
std::shared_ptr<Main::SessionShow> uiShow) {
_uiShow = std::move(uiShow);
}
void PeerListWidgetsDelegate::peerListSetHideEmpty(bool hide) {
Unexpected("...PeerListWidgetsDelegate::peerListSetHideEmpty");
}
void PeerListWidgetsDelegate::peerListAppendRow(
std::unique_ptr<PeerListRow> row) {
_content->appendRow(std::move(row));
}
void PeerListWidgetsDelegate::peerListAppendSearchRow(
std::unique_ptr<PeerListRow> row) {
Unexpected("...PeerListWidgetsDelegate::peerListAppendSearchRow");
}
void PeerListWidgetsDelegate::peerListAppendFoundRow(
not_null<PeerListRow*> row) {
Unexpected("...PeerListWidgetsDelegate::peerListAppendFoundRow");
}
void PeerListWidgetsDelegate::peerListPrependRow(
std::unique_ptr<PeerListRow> row) {
Unexpected("...PeerListWidgetsDelegate::peerListPrependRow");
}
void PeerListWidgetsDelegate::peerListPrependRowFromSearchResult(
not_null<PeerListRow*> row) {
Unexpected(
"...PeerListWidgetsDelegate::peerListPrependRowFromSearchResult");
}
PeerListRow* PeerListWidgetsDelegate::peerListFindRow(PeerListRowId id) {
return _content->findRow(id);
}
auto PeerListWidgetsDelegate::peerListLastRowMousePosition()
-> std::optional<QPoint> {
Unexpected("...PeerListWidgetsDelegate::peerListLastRowMousePosition");
}
void PeerListWidgetsDelegate::peerListUpdateRow(not_null<PeerListRow*> row) {
_content->updateRow(row);
}
void PeerListWidgetsDelegate::peerListRemoveRow(not_null<PeerListRow*> row) {
Unexpected("...PeerListWidgetsDelegate::peerListRemoveRow");
}
void PeerListWidgetsDelegate::peerListConvertRowToSearchResult(
not_null<PeerListRow*> row) {
Unexpected(
"...PeerListWidgetsDelegate::peerListConvertRowToSearchResult");
}
void PeerListWidgetsDelegate::peerListSetRowChecked(
not_null<PeerListRow*> row,
bool checked) {
Unexpected("...PeerListWidgetsDelegate::peerListSetRowChecked");
}
void PeerListWidgetsDelegate::peerListSetRowHidden(
not_null<PeerListRow*> row,
bool hidden) {
Unexpected("...PeerListWidgetsDelegate::peerListSetRowHidden");
}
void PeerListWidgetsDelegate::peerListSetForeignRowChecked(
not_null<PeerListRow*> row,
bool checked,
anim::type animated) {
}
int PeerListWidgetsDelegate::peerListFullRowsCount() {
return _content->fullRowsCount();
}
not_null<PeerListRow*> PeerListWidgetsDelegate::peerListRowAt(int index) {
return _content->rowAt(index);
}
int PeerListWidgetsDelegate::peerListSearchRowsCount() {
Unexpected("...PeerListWidgetsDelegate::peerListSearchRowsCount");
}
not_null<PeerListRow*> PeerListWidgetsDelegate::peerListSearchRowAt(int) {
Unexpected("...PeerListWidgetsDelegate::peerListSearchRowAt");
}
void PeerListWidgetsDelegate::peerListRefreshRows() {
_content->refreshRows();
}
void PeerListWidgetsDelegate::peerListSetDescription(
object_ptr<Ui::FlatLabel>) {
Unexpected("...PeerListWidgetsDelegate::peerListSetDescription");
}
void PeerListWidgetsDelegate::peerListSetSearchNoResults(
object_ptr<Ui::FlatLabel>) {
Unexpected("...PeerListWidgetsDelegate::peerListSetSearchNoResults");
}
void PeerListWidgetsDelegate::peerListSetAboveWidget(
object_ptr<Ui::RpWidget>) {
Unexpected("...PeerListWidgetsDelegate::peerListSetAboveWidget");
}
void PeerListWidgetsDelegate::peerListSetAboveSearchWidget(
object_ptr<Ui::RpWidget>) {
Unexpected("...PeerListWidgetsDelegate::peerListSetAboveSearchWidget");
}
void PeerListWidgetsDelegate::peerListSetBelowWidget(
object_ptr<Ui::RpWidget>) {
Unexpected("...PeerListWidgetsDelegate::peerListSetBelowWidget");
}
void PeerListWidgetsDelegate::peerListSetSearchMode(PeerListSearchMode mode) {
Unexpected("...PeerListWidgetsDelegate::peerListSetSearchMode");
}
void PeerListWidgetsDelegate::peerListMouseLeftGeometry() {
Unexpected("...PeerListWidgetsDelegate::peerListMouseLeftGeometry");
}
void PeerListWidgetsDelegate::peerListSortRows(
Fn<bool(const PeerListRow &, const PeerListRow &)>) {
Unexpected("...PeerListWidgetsDelegate::peerListSortRows");
}
int PeerListWidgetsDelegate::peerListPartitionRows(
Fn<bool(const PeerListRow &a)> border) {
Unexpected("...PeerListWidgetsDelegate::peerListPartitionRows");
}
State PeerListWidgetsDelegate::peerListSaveState() const {
Unexpected("...PeerListWidgetsDelegate::peerListSaveState");
return nullptr;
}
void PeerListWidgetsDelegate::peerListRestoreState(
std::unique_ptr<PeerListState> state) {
Unexpected("...PeerListWidgetsDelegate::peerListRestoreState");
}
void PeerListWidgetsDelegate::peerListShowRowMenu(
not_null<PeerListRow*> row,
bool highlightRow,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) {
}
void PeerListWidgetsDelegate::peerListSelectSkip(int direction) {
// _content->selectSkip(direction);
}
void PeerListWidgetsDelegate::peerListPressLeftToContextMenu(bool shown) {
Unexpected("...PeerListWidgetsDelegate::peerListPressLeftToContextMenu");
}
bool PeerListWidgetsDelegate::peerListTrackRowPressFromGlobal(QPoint) {
return false;
}
std::shared_ptr<Main::SessionShow> PeerListWidgetsDelegate::peerListUiShow() {
Expects(_uiShow != nullptr);
return _uiShow;
}
void PeerListWidgetsDelegate::peerListAddSelectedPeerInBunch(
not_null<PeerData*> peer) {
Unexpected("...PeerListWidgetsDelegate::peerListAddSelectedPeerInBunch");
}
void PeerListWidgetsDelegate::peerListAddSelectedRowInBunch(
not_null<PeerListRow*> row) {
Unexpected("...PeerListWidgetsDelegate::peerListAddSelectedRowInBunch");
}
void PeerListWidgetsDelegate::peerListFinishSelectedRowsBunch() {
Unexpected("...PeerListWidgetsDelegate::peerListFinishSelectedRowsBunch");
}
void PeerListWidgetsDelegate::peerListSetTitle(rpl::producer<QString> title) {
Unexpected("...PeerListWidgetsDelegate::peerListSetTitle");
}
void PeerListWidgetsDelegate::peerListSetAdditionalTitle(
rpl::producer<QString> title) {
Unexpected("...PeerListWidgetsDelegate::peerListSetAdditionalTitle");
}
bool PeerListWidgetsDelegate::peerListIsRowChecked(
not_null<PeerListRow*> row) {
Unexpected("...PeerListWidgetsDelegate::peerListIsRowChecked");
return false;
}
void PeerListWidgetsDelegate::peerListScrollToTop() {
Unexpected("...PeerListWidgetsDelegate::peerListScrollToTop");
}
int PeerListWidgetsDelegate::peerListSelectedRowsCount() {
Unexpected("...PeerListWidgetsDelegate::peerListSelectedRowsCount");
return 0;
}

View file

@ -0,0 +1,110 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "boxes/peer_list_box.h"
namespace Ui {
class VerticalLayout;
} // namespace Ui
class PeerListWidgets : public Ui::RpWidget {
public:
PeerListWidgets(
not_null<Ui::RpWidget*> parent,
not_null<PeerListController*> controller);
crl::time paintRow(
Painter &p,
crl::time now,
bool selected,
not_null<PeerListRow*> row);
void appendRow(std::unique_ptr<PeerListRow> row);
PeerListRow* findRow(PeerListRowId id);
void updateRow(not_null<PeerListRow*> row);
int fullRowsCount();
[[nodiscard]] not_null<PeerListRow*> rowAt(int index);
void refreshRows();
private:
const not_null<PeerListController*> _controller;
const style::PeerList &_st;
base::unique_qptr<Ui::VerticalLayout> _content;
std::vector<std::unique_ptr<PeerListRow>> _rows;
std::map<PeerListRowId, not_null<PeerListRow*>> _rowsById;
std::map<PeerData*, std::vector<not_null<PeerListRow*>>> _rowsByPeer;
};
class PeerListWidgetsDelegate : public PeerListDelegate {
public:
void setContent(PeerListWidgets *content);
void setUiShow(std::shared_ptr<Main::SessionShow> uiShow);
void peerListSetHideEmpty(bool hide) override;
void peerListAppendRow(std::unique_ptr<PeerListRow> row) override;
void peerListAppendSearchRow(std::unique_ptr<PeerListRow> row) override;
void peerListAppendFoundRow(not_null<PeerListRow*> row) override;
void peerListPrependRow(std::unique_ptr<PeerListRow> row) override;
void peerListPrependRowFromSearchResult(
not_null<PeerListRow*> row) override;
PeerListRow *peerListFindRow(PeerListRowId id) override;
std::optional<QPoint> peerListLastRowMousePosition() override;
void peerListUpdateRow(not_null<PeerListRow*> row) override;
void peerListRemoveRow(not_null<PeerListRow*> row) override;
void peerListConvertRowToSearchResult(
not_null<PeerListRow*> row) override;
void peerListSetRowChecked(
not_null<PeerListRow*> row,
bool checked) override;
void peerListSetRowHidden(
not_null<PeerListRow*> row,
bool hidden) override;
void peerListSetForeignRowChecked(
not_null<PeerListRow*> row,
bool checked,
anim::type animated) override;
int peerListFullRowsCount() override;
not_null<PeerListRow*> peerListRowAt(int index) override;
int peerListSearchRowsCount() override;
not_null<PeerListRow*> peerListSearchRowAt(int index) override;
void peerListRefreshRows() override;
void peerListSetDescription(object_ptr<Ui::FlatLabel>) override;
void peerListSetSearchNoResults(object_ptr<Ui::FlatLabel>) override;
void peerListSetAboveWidget(object_ptr<Ui::RpWidget>) override;
void peerListSetAboveSearchWidget(object_ptr<Ui::RpWidget>) override;
void peerListSetBelowWidget(object_ptr<Ui::RpWidget>) override;
void peerListSetSearchMode(PeerListSearchMode mode) override;
void peerListMouseLeftGeometry() override;
void peerListSortRows(
Fn<bool(const PeerListRow &, const PeerListRow &)>) override;
int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) override;
std::unique_ptr<PeerListState> peerListSaveState() const override;
void peerListRestoreState(std::unique_ptr<PeerListState> state) override;
void peerListShowRowMenu(
not_null<PeerListRow*> row,
bool highlightRow,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override;
void peerListSelectSkip(int direction) override;
void peerListPressLeftToContextMenu(bool shown) override;
bool peerListTrackRowPressFromGlobal(QPoint globalPosition) override;
std::shared_ptr<Main::SessionShow> peerListUiShow() override;
void peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) override;
void peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) override;
void peerListFinishSelectedRowsBunch() override;
void peerListSetTitle(rpl::producer<QString> title) override;
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
void peerListScrollToTop() override;
int peerListSelectedRowsCount() override;
private:
PeerListWidgets *_content = nullptr;
std::shared_ptr<Main::SessionShow> _uiShow;
};

View file

@ -26,17 +26,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_credits_graphics.h" #include "settings/settings_credits_graphics.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h" #include "ui/controls/userpic_button.h"
#include "ui/effects/credits_graphics.h"
#include "ui/effects/premium_graphics.h" #include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg. #include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg.
#include "ui/image/image_prepare.h" #include "ui/image/image_prepare.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/rect.h" #include "ui/rect.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/vertical_list.h" #include "ui/vertical_list.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/peer_bubble.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "styles/style_credits.h" #include "styles/style_credits.h"
#include "styles/style_giveaway.h" #include "styles/style_giveaway.h"
#include "styles/style_info.h" // inviteLinkSubscribeBoxTerms
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_premium.h" #include "styles/style_premium.h"
#include "styles/style_settings.h" #include "styles/style_settings.h"
@ -92,6 +97,44 @@ struct PaidMediaData {
}; };
} }
void AddTerms(
not_null<Ui::BoxContent*> box,
not_null<Ui::RpWidget*> button,
const style::Box &stBox) {
const auto terms = Ui::CreateChild<Ui::FlatLabel>(
button->parentWidget(),
tr::lng_channel_invite_subscription_terms(
lt_link,
rpl::combine(
tr::lng_paid_react_agree_link(),
tr::lng_group_invite_subscription_about_url()
) | rpl::map([](const QString &text, const QString &url) {
return Ui::Text::Link(text, url);
}),
Ui::Text::RichLangValue),
st::inviteLinkSubscribeBoxTerms);
const auto &buttonPadding = stBox.buttonPadding;
const auto style = box->lifetime().make_state<style::Box>(style::Box{
.buttonPadding = buttonPadding + QMargins(0, 0, 0, terms->height()),
.buttonHeight = stBox.buttonHeight,
.button = stBox.button,
.margin = stBox.margin,
.title = stBox.title,
.bg = stBox.bg,
.titleAdditionalFg = stBox.titleAdditionalFg,
.shadowIgnoreTopSkip = stBox.shadowIgnoreTopSkip,
.shadowIgnoreBottomSkip = stBox.shadowIgnoreBottomSkip,
});
button->geometryValue() | rpl::start_with_next([=](const QRect &rect) {
terms->resizeToWidth(box->width()
- rect::m::sum::h(st::boxRowPadding));
terms->moveToLeft(
rect.x() + (rect.width() - terms->width()) / 2,
rect::bottom(rect) + buttonPadding.bottom() / 2);
}, terms->lifetime());
box->setStyle(*style);
}
[[nodiscard]] rpl::producer<TextWithEntities> SendCreditsConfirmText( [[nodiscard]] rpl::producer<TextWithEntities> SendCreditsConfirmText(
not_null<Main::Session*> session, not_null<Main::Session*> session,
not_null<Payments::CreditsFormData*> form) { not_null<Payments::CreditsFormData*> form) {
@ -150,6 +193,18 @@ struct PaidMediaData {
} }
const auto bot = session->data().user(form->botId); const auto bot = session->data().user(form->botId);
if (form->invoice.subscriptionPeriod) {
return (bot->botInfo
? tr::lng_credits_box_out_subscription_bot
: tr::lng_credits_box_out_subscription_business)(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
lt_title,
rpl::single(TextWithEntities{ form->title }),
lt_recipient,
rpl::single(TextWithEntities{ bot->name() }),
Ui::Text::RichLangValue);
}
return tr::lng_credits_box_out_sure( return tr::lng_credits_box_out_sure(
lt_count, lt_count,
rpl::single(form->invoice.amount) | tr::to_count(), rpl::single(form->invoice.amount) | tr::to_count(),
@ -190,6 +245,57 @@ struct PaidMediaData {
st::defaultUserpicButton); st::defaultUserpicButton);
} }
[[nodiscard]] not_null<Ui::RpWidget*> SendCreditsBadge(
not_null<Ui::RpWidget*> parent,
int credits) {
const auto widget = Ui::CreateChild<Ui::RpWidget>(parent);
const auto &font = st::chatGiveawayBadgeFont;
const auto text = QString::number(credits);
const auto iconHeight = font->ascent - font->descent;
const auto iconWidth = iconHeight + st::lineWidth;
const auto width = font->width(text) + iconWidth + st::lineWidth;
const auto inner = QRect(0, 0, width, font->height);
const auto rect = inner + st::subscriptionCreditsBadgePadding;
const auto size = rect.size();
const auto svg = widget->lifetime().make_state<QSvgRenderer>(
Ui::Premium::Svg());
const auto half = st::chatGiveawayBadgeStroke / 2.;
const auto left = st::subscriptionCreditsBadgePadding.left();
const auto smaller = QRectF(rect.translated(-rect.topLeft()))
- Margins(half);
const auto radius = smaller.height() / 2.;
widget->resize(size);
widget->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(widget);
auto hq = PainterHighQualityEnabler(p);
p.setPen(QPen(st::premiumButtonFg, st::chatGiveawayBadgeStroke * 1.));
p.setBrush(st::creditsBg3);
p.drawRoundedRect(smaller, radius, radius);
p.translate(0, font->descent / 2);
p.setPen(st::premiumButtonFg);
p.setBrush(st::premiumButtonFg);
svg->render(
&p,
QRect(
left,
half + (inner.height() - iconHeight) / 2,
iconHeight,
iconHeight));
p.setFont(font);
p.drawText(
left + iconWidth,
st::subscriptionCreditsBadgePadding.top() + font->ascent,
text);
}, widget->lifetime());
return widget;
}
} // namespace } // namespace
void SendCreditsBox( void SendCreditsBox(
@ -203,7 +309,8 @@ void SendCreditsBox(
rpl::variable<bool> confirmButtonBusy = false; rpl::variable<bool> confirmButtonBusy = false;
}; };
const auto state = box->lifetime().make_state<State>(); const auto state = box->lifetime().make_state<State>();
box->setStyle(st::giveawayGiftCodeBox); const auto &stBox = st::giveawayGiftCodeBox;
box->setStyle(stBox);
box->setNoContentMargin(true); box->setNoContentMargin(true);
const auto session = form->invoice.session; const auto session = form->invoice.session;
@ -245,14 +352,36 @@ void SendCreditsBox(
content, content,
SendCreditsThumbnail(content, session, form.get(), photoSize))); SendCreditsThumbnail(content, session, form.get(), photoSize)));
thumb->setAttribute(Qt::WA_TransparentForMouseEvents); thumb->setAttribute(Qt::WA_TransparentForMouseEvents);
if (form->invoice.subscriptionPeriod) {
const auto badge = SendCreditsBadge(content, form->invoice.amount);
thumb->geometryValue() | rpl::start_with_next([=](const QRect &r) {
badge->moveToLeft(
r.x() + (r.width() - badge->width()) / 2,
rect::bottom(r) - badge->height() / 2);
}, badge->lifetime());
Ui::AddSkip(content);
Ui::AddSkip(content);
}
Ui::AddSkip(content); Ui::AddSkip(content);
box->addRow(object_ptr<Ui::CenterWrap<>>( box->addRow(object_ptr<Ui::CenterWrap<>>(
box, box,
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
box, box,
tr::lng_credits_box_out_title(), form->invoice.subscriptionPeriod
? rpl::single(form->title)
: tr::lng_credits_box_out_title(),
st::settingsPremiumUserTitle))); st::settingsPremiumUserTitle)));
if (form->invoice.subscriptionPeriod && form->botId && form->photo) {
Ui::AddSkip(content);
Ui::AddSkip(content);
const auto bot = session->data().user(form->botId);
box->addRow(
object_ptr<Ui::CenterWrap<>>(
box,
Ui::CreatePeerBubble(box, bot)));
Ui::AddSkip(content);
}
Ui::AddSkip(content); Ui::AddSkip(content);
box->addRow(object_ptr<Ui::CenterWrap<>>( box->addRow(object_ptr<Ui::CenterWrap<>>(
box, box,
@ -306,6 +435,9 @@ void SendCreditsBox(
} }
}).send(); }).send();
}); });
if (form->invoice.subscriptionPeriod) {
AddTerms(box, button, stBox);
}
{ {
using namespace Info::Statistics; using namespace Info::Statistics;
const auto loadingAnimation = InfiniteRadialAnimationWidget( const auto loadingAnimation = InfiniteRadialAnimationWidget(
@ -317,12 +449,14 @@ void SendCreditsBox(
SetButtonMarkedLabel( SetButtonMarkedLabel(
button, button,
rpl::combine( rpl::combine(
tr::lng_credits_box_out_confirm( (form->invoice.subscriptionPeriod
lt_count, ? tr::lng_credits_box_out_subscription_confirm
rpl::single(form->invoice.amount) | tr::to_count(), : tr::lng_credits_box_out_confirm)(
lt_emoji, lt_count,
rpl::single(CreditsEmojiSmall(session)), rpl::single(form->invoice.amount) | tr::to_count(),
Ui::Text::RichLangValue), lt_emoji,
rpl::single(CreditsEmojiSmall(session)),
Ui::Text::RichLangValue),
state->confirmButtonBusy.value() state->confirmButtonBusy.value()
) | rpl::map([](TextWithEntities &&text, bool busy) { ) | rpl::map([](TextWithEntities &&text, bool busy) {
return busy ? TextWithEntities() : std::move(text); return busy ? TextWithEntities() : std::move(text);
@ -332,7 +466,7 @@ void SendCreditsBox(
box->getDelegate()->style().button.textFg->c); box->getDelegate()->style().button.textFg->c);
const auto buttonWidth = st::boxWidth const auto buttonWidth = st::boxWidth
- rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding); - rect::m::sum::h(stBox.buttonPadding);
button->widthValue() | rpl::filter([=] { button->widthValue() | rpl::filter([=] {
return (button->widthNoMargins() != buttonWidth); return (button->widthNoMargins() != buttonWidth);
}) | rpl::start_with_next([=] { }) | rpl::start_with_next([=] {
@ -341,15 +475,11 @@ void SendCreditsBox(
{ {
const auto close = Ui::CreateChild<Ui::IconButton>( const auto close = Ui::CreateChild<Ui::IconButton>(
box.get(), content,
st::boxTitleClose); st::boxTitleClose);
close->setClickedCallback([=] { close->setClickedCallback([=] { box->closeBox(); });
box->closeBox(); content->widthValue() | rpl::start_with_next([=](int) {
});
box->widthValue(
) | rpl::start_with_next([=](int width) {
close->moveToRight(0, 0); close->moveToRight(0, 0);
close->raise();
}, close->lifetime()); }, close->lifetime());
} }

View file

@ -669,7 +669,8 @@ bool Instance::inCall() const {
return false; return false;
} }
const auto state = _currentCall->state(); const auto state = _currentCall->state();
return (state != Call::State::Busy); return (state != Call::State::Busy)
&& (state != Call::State::WaitingUserConfirmation);
} }
bool Instance::inGroupCall() const { bool Instance::inGroupCall() const {

View file

@ -993,7 +993,12 @@ void Panel::paint(QRect clip) {
bool Panel::handleClose() const { bool Panel::handleClose() const {
if (_call) { if (_call) {
window()->hide(); if (_call->state() == Call::State::WaitingUserConfirmation
|| _call->state() == Call::State::Busy) {
_call->hangup();
} else {
window()->hide();
}
return true; return true;
} }
return false; return false;
@ -1028,6 +1033,7 @@ void Panel::stateChanged(State state) {
_startVideo = base::make_unique_q<Ui::CallButton>( _startVideo = base::make_unique_q<Ui::CallButton>(
widget(), widget(),
st::callStartVideo); st::callStartVideo);
_startVideo->show();
_startVideo->setText(tr::lng_call_start_video()); _startVideo->setText(tr::lng_call_start_video());
_startVideo->clicks() | rpl::map_to(true) | rpl::start_to_stream( _startVideo->clicks() | rpl::map_to(true) | rpl::start_to_stream(
_startOutgoingRequests, _startOutgoingRequests,

View file

@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs; constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs; constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs; constexpr auto AppFile = "AyuGram"_cs;
constexpr auto AppVersion = 5008002; constexpr auto AppVersion = 5008003;
constexpr auto AppVersionStr = "5.8.2"; constexpr auto AppVersionStr = "5.8.3";
constexpr auto AppBetaVersion = false; constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -1980,6 +1980,19 @@ rpl::producer<> Session::pinnedDialogsOrderUpdated() const {
return _pinnedDialogsOrderUpdated.events(); return _pinnedDialogsOrderUpdated.events();
} }
Session::CreditsSubsRebuilderPtr Session::createCreditsSubsRebuilder() {
if (auto result = activeCreditsSubsRebuilder()) {
return result;
}
auto result = std::make_shared<CreditsSubsRebuilder>();
_creditsSubsRebuilder = result;
return result;
}
Session::CreditsSubsRebuilderPtr Session::activeCreditsSubsRebuilder() const {
return _creditsSubsRebuilder.lock();
}
void Session::registerHeavyViewPart(not_null<ViewElement*> view) { void Session::registerHeavyViewPart(not_null<ViewElement*> view) {
_heavyViewParts.emplace(view); _heavyViewParts.emplace(view);
} }

View file

@ -70,6 +70,7 @@ class Chatbots;
class BusinessInfo; class BusinessInfo;
struct ReactionId; struct ReactionId;
struct UnavailableReason; struct UnavailableReason;
struct CreditsStatusSlice;
struct RepliesReadTillUpdate { struct RepliesReadTillUpdate {
FullMsgId id; FullMsgId id;
@ -337,6 +338,11 @@ public:
void notifyPinnedDialogsOrderUpdated(); void notifyPinnedDialogsOrderUpdated();
[[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const; [[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const;
using CreditsSubsRebuilder = rpl::event_stream<Data::CreditsStatusSlice>;
using CreditsSubsRebuilderPtr = std::shared_ptr<CreditsSubsRebuilder>;
[[nodiscard]] CreditsSubsRebuilderPtr createCreditsSubsRebuilder();
[[nodiscard]] CreditsSubsRebuilderPtr activeCreditsSubsRebuilder() const;
void registerRestricted( void registerRestricted(
not_null<const HistoryItem*> item, not_null<const HistoryItem*> item,
const QString &reason); const QString &reason);
@ -1095,6 +1101,8 @@ private:
MessageIdsList _mimeForwardIds; MessageIdsList _mimeForwardIds;
std::weak_ptr<CreditsSubsRebuilder> _creditsSubsRebuilder;
using CredentialsWithGeneration = std::pair< using CredentialsWithGeneration = std::pair<
const Passport::SavedCredentials, const Passport::SavedCredentials,
int>; int>;

View file

@ -18,6 +18,7 @@ struct PeerSubscription final {
} }
}; };
using PhotoId = uint64;
struct SubscriptionEntry final { struct SubscriptionEntry final {
explicit operator bool() const { explicit operator bool() const {
return !id.isEmpty(); return !id.isEmpty();
@ -25,10 +26,14 @@ struct SubscriptionEntry final {
QString id; QString id;
QString inviteHash; QString inviteHash;
QString title;
QString slug;
QDateTime until; QDateTime until;
PeerSubscription subscription; PeerSubscription subscription;
uint64 barePeerId = 0; uint64 barePeerId = 0;
PhotoId photoId = PhotoId(0);
bool cancelled = false; bool cancelled = false;
bool cancelledByBot = false;
bool expired = false; bool expired = false;
bool canRefulfill = false; bool canRefulfill = false;
}; };

View file

@ -295,7 +295,7 @@ auto Entry::unreadStateChangeNotifier(bool required) {
_flags |= Flag::InUnreadChangeBlock; _flags |= Flag::InUnreadChangeBlock;
const auto notify = required && inChatList(); const auto notify = required && inChatList();
const auto wasState = notify ? chatListUnreadState() : UnreadState(); const auto wasState = notify ? chatListUnreadState() : UnreadState();
return gsl::finally([=] { return gsl::finally([=, this] {
_flags &= ~Flag::InUnreadChangeBlock; _flags &= ~Flag::InUnreadChangeBlock;
if (notify) { if (notify) {
Assert(inChatList()); Assert(inChatList());

View file

@ -130,19 +130,6 @@ int BinarySearchBlocksOrItems(const T &list, int edge) {
return start; return start;
} }
[[nodiscard]] bool CanSendReply(not_null<const HistoryItem*> item) {
if (item->isDeleted()) {
return false;
}
const auto peer = item->history()->peer;
const auto topic = item->topic();
return topic
? Data::CanSendAnything(topic)
: (Data::CanSendAnything(peer)
&& (!peer->isChannel() || peer->asChannel()->amIn()));
}
} // namespace } // namespace
// flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html
@ -631,21 +618,13 @@ void HistoryInner::setupSwipeReply() {
const auto replyToItemId = (selected.item const auto replyToItemId = (selected.item
? selected.item ? selected.item
: still)->fullId(); : still)->fullId();
if (canSendReply) { _widget->replyToMessage({
_widget->replyToMessage({ .messageId = replyToItemId,
.messageId = replyToItemId, .quote = selected.text,
.quote = selected.text, .quoteOffset = selected.offset,
.quoteOffset = selected.offset, });
}); if (!selected.text.empty()) {
if (!selected.text.empty()) { _widget->clearSelected();
_widget->clearSelected();
}
} else {
HistoryView::Controls::ShowReplyToChatBox(show, {
.messageId = replyToItemId,
.quote = selected.text,
.quoteOffset = selected.offset,
});
} }
}; };
return false; return false;
@ -2644,26 +2623,13 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
const auto quoteOffset = selected.offset; const auto quoteOffset = selected.offset;
text.replace('&', u"&&"_q); text.replace('&', u"&&"_q);
_menu->addAction(text, [=] { _menu->addAction(text, [=] {
const auto still = session->data().message(itemId); _widget->replyToMessage({
const auto forceAnotherChat = base::IsCtrlPressed() .messageId = itemId,
&& still .quote = quote,
&& still->allowsForward(); .quoteOffset = quoteOffset,
if (canSendReply && !forceAnotherChat) { });
_widget->replyToMessage({ if (!quote.empty()) {
.messageId = itemId, _widget->clearSelected();
.quote = quote,
.quoteOffset = quoteOffset,
});
if (!quote.empty()) {
_widget->clearSelected();
}
} else {
const auto show = controller->uiShow();
HistoryView::Controls::ShowReplyToChatBox(show, {
.messageId = itemId,
.quote = quote,
.quoteOffset = quoteOffset,
});
} }
}, &st::menuIconReply); }, &st::menuIconReply);
} }
@ -4847,3 +4813,16 @@ auto HistoryInner::DelegateMixin()
-> std::unique_ptr<HistoryMainElementDelegateMixin> { -> std::unique_ptr<HistoryMainElementDelegateMixin> {
return std::make_unique<HistoryMainElementDelegate>(); return std::make_unique<HistoryMainElementDelegate>();
} }
bool CanSendReply(not_null<const HistoryItem*> item) {
if (item->isDeleted()) {
return false;
}
const auto peer = item->history()->peer;
const auto topic = item->topic();
return topic
? Data::CanSendAnything(topic)
: (Data::CanSendAnything(peer)
&& (!peer->isChannel() || peer->asChannel()->amIn()));
}

View file

@ -555,3 +555,5 @@ private:
bool _wasForceClickPreview = false; bool _wasForceClickPreview = false;
}; };
[[nodiscard]] bool CanSendReply(not_null<const HistoryItem*> item);

View file

@ -4701,6 +4701,21 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
return preparePaymentSentText(); return preparePaymentSentText();
}; };
auto preparePaymentSentMe = [&](const MTPDmessageActionPaymentSentMe &data) {
auto result = PreparedServiceText();
result.text = (data.is_recurring_used()
? tr::lng_action_payment_bot_recurring
: tr::lng_action_payment_bot_done)(
tr::now,
lt_amount,
AmountAndStarCurrency(
&_history->session(),
data.vtotal_amount().v,
qs(data.vcurrency())),
Ui::Text::WithEntities);
return result;
};
auto prepareScreenshotTaken = [this](const MTPDmessageActionScreenshotTaken &) { auto prepareScreenshotTaken = [this](const MTPDmessageActionScreenshotTaken &) {
auto result = PreparedServiceText(); auto result = PreparedServiceText();
if (out()) { if (out()) {
@ -5504,7 +5519,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
prepareSecureValuesSent, prepareSecureValuesSent,
prepareContactSignUp, prepareContactSignUp,
prepareProximityReached, prepareProximityReached,
PrepareErrorText<MTPDmessageActionPaymentSentMe>, preparePaymentSentMe,
PrepareErrorText<MTPDmessageActionSecureValuesSentMe>, PrepareErrorText<MTPDmessageActionSecureValuesSentMe>,
prepareGroupCall, prepareGroupCall,
prepareInviteToGroupCall, prepareInviteToGroupCall,

View file

@ -2028,24 +2028,12 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(
UserData *samePeerBot, UserData *samePeerBot,
MsgId samePeerReplyTo) { MsgId samePeerReplyTo) {
if (samePeerBot) { if (samePeerBot) {
if (_history) { const auto to = controller()->currentDialogsEntryState();
const auto textWithTags = TextWithTags{ if (!to.key.owningHistory()) {
'@' + samePeerBot->username() + ' ' + query, return false;
TextWithTags::Tags(),
};
MessageCursor cursor = {
int(textWithTags.text.size()),
int(textWithTags.text.size()),
Ui::kQFixedMax,
};
_history->setLocalDraft(std::make_unique<Data::Draft>(
textWithTags,
FullReplyTo(),
cursor,
Data::WebPageDraft()));
applyDraft();
return true;
} }
controller()->switchInlineQuery(to, samePeerBot, query);
return true;
} else if (const auto bot = _peer ? _peer->asUser() : nullptr) { } else if (const auto bot = _peer ? _peer->asUser() : nullptr) {
const auto to = bot->isBot() const auto to = bot->isBot()
? bot->botInfo->inlineReturnTo ? bot->botInfo->inlineReturnTo
@ -2273,7 +2261,7 @@ void HistoryWidget::showHistory(
_showAtMsgHighlightPart = {}; _showAtMsgHighlightPart = {};
_showAtMsgHighlightPartOffsetHint = 0; _showAtMsgHighlightPartOffsetHint = 0;
const auto wasDialogsEntryState = computeDialogsEntryState(); const auto wasState = controller()->currentDialogsEntryState();
const auto startBot = (showAtMsgId == ShowAndStartBotMsgId); const auto startBot = (showAtMsgId == ShowAndStartBotMsgId);
if (startBot) { if (startBot) {
showAtMsgId = ShowAtTheEndMsgId; showAtMsgId = ShowAtTheEndMsgId;
@ -2378,8 +2366,8 @@ void HistoryWidget::showHistory(
if (const auto user = _peer->asUser()) { if (const auto user = _peer->asUser()) {
if (const auto &info = user->botInfo) { if (const auto &info = user->botInfo) {
if (startBot) { if (startBot) {
if (wasDialogsEntryState.key) { if (wasState.key) {
info->inlineReturnTo = wasDialogsEntryState; info->inlineReturnTo = wasState;
} }
sendBotStartCommand(); sendBotStartCommand();
_history->clearLocalDraft({}); _history->clearLocalDraft({});
@ -2614,8 +2602,8 @@ void HistoryWidget::showHistory(
if (const auto user = _peer->asUser()) { if (const auto user = _peer->asUser()) {
if (const auto &info = user->botInfo) { if (const auto &info = user->botInfo) {
if (startBot) { if (startBot) {
if (wasDialogsEntryState.key) { if (wasState.key) {
info->inlineReturnTo = wasDialogsEntryState; info->inlineReturnTo = wasState;
} }
sendBotStartCommand(); sendBotStartCommand();
} }
@ -8060,7 +8048,15 @@ void HistoryWidget::clearFieldText(
void HistoryWidget::replyToMessage(FullReplyTo id) { void HistoryWidget::replyToMessage(FullReplyTo id) {
if (const auto item = session().data().message(id.messageId)) { if (const auto item = session().data().message(id.messageId)) {
replyToMessage(item, id.quote, id.quoteOffset); if (CanSendReply(item) && !base::IsCtrlPressed()) {
replyToMessage(item, id.quote, id.quoteOffset);
} else if (item->allowsForward()) {
const auto show = controller()->uiShow();
HistoryView::Controls::ShowReplyToChatBox(show, id);
} else {
controller()->showToast(
tr::lng_error_cant_reply_other(tr::now));
}
} }
} }

View file

@ -1887,7 +1887,7 @@ not_null<Ui::PathShiftGradient*> ListWidget::elementPathShiftGradient() {
} }
void ListWidget::elementReplyTo(const FullReplyTo &to) { void ListWidget::elementReplyTo(const FullReplyTo &to) {
replyToMessageRequestNotify(to); replyToMessageRequestNotify(to, base::IsCtrlPressed());
} }
void ListWidget::elementStartInteraction(not_null<const Element*> view) { void ListWidget::elementStartInteraction(not_null<const Element*> view) {

View file

@ -1659,7 +1659,8 @@ void Message::draw(Painter &p, const PaintContext &context) const {
(right (right
- (st::msgSelectionOffset * progress - st.size) / 2 - (st::msgSelectionOffset * progress - st.size) / 2
- st::msgPadding.right() / 2 - st::msgPadding.right() / 2
- st.size), - st.size
- st::historyScroll.deltax),
rect::bottom(g) - st.size - st::msgSelectionBottomSkip); rect::bottom(g) - st.size - st::msgSelectionBottomSkip);
{ {
p.setPen(QPen(st.border, st.width)); p.setPen(QPen(st.border, st.width));

View file

@ -255,7 +255,6 @@ DocumentData *Sticker::document() {
void Sticker::stickerClearLoopPlayed() { void Sticker::stickerClearLoopPlayed() {
_oncePlayed = false; _oncePlayed = false;
_premiumEffectPlayed = false;
_premiumEffectSkipped = false; _premiumEffectSkipped = false;
} }

View file

@ -634,10 +634,13 @@ QSize WebPage::countOptimalSize() {
_durationWidth = st::msgDateFont->width(_duration); _durationWidth = st::msgDateFont->width(_duration);
} }
if (!_openButton.isEmpty()) { if (!_openButton.isEmpty()) {
accumulate_max( const auto w = rect::m::sum::h(st::historyPageButtonPadding)
maxWidth, + _openButton.maxWidth();
rect::m::sum::h(st::historyPageButtonPadding) if (sponsored) {
+ _openButton.maxWidth()); accumulate_max(maxWidth, w);
} else {
maxWidth += w;
}
} }
maxWidth += rect::m::sum::h(padding); maxWidth += rect::m::sum::h(padding);
minHeight += rect::m::sum::v(padding); minHeight += rect::m::sum::v(padding);

View file

@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "styles/style_color_indices.h"
#include "styles/style_credits.h" #include "styles/style_credits.h"
#include "styles/style_giveaway.h" #include "styles/style_giveaway.h"
#include "styles/style_info.h" #include "styles/style_info.h"
@ -59,7 +60,6 @@ namespace {
constexpr auto kDoneTooltipDuration = 5 * crl::time(1000); constexpr auto kDoneTooltipDuration = 5 * crl::time(1000);
constexpr auto kAdditionalPrizeLengthMax = 128; constexpr auto kAdditionalPrizeLengthMax = 128;
constexpr auto kColorIndexCredits = int(1);
[[nodiscard]] QDateTime ThreeDaysAfterToday() { [[nodiscard]] QDateTime ThreeDaysAfterToday() {
auto dateNow = QDateTime::currentDateTime(); auto dateNow = QDateTime::currentDateTime();
@ -370,7 +370,7 @@ void CreateGiveawayBox(
prepaid->credits prepaid->credits
? GiveawayType::PrepaidCredits ? GiveawayType::PrepaidCredits
: GiveawayType::Prepaid, : GiveawayType::Prepaid,
prepaid->credits ? kColorIndexCredits : prepaid->id, prepaid->credits ? st::colorIndexOrange : prepaid->id,
tr::lng_boosts_prepaid_giveaway_single(), tr::lng_boosts_prepaid_giveaway_single(),
prepaid->credits prepaid->credits
? tr::lng_boosts_prepaid_giveaway_credits_status( ? tr::lng_boosts_prepaid_giveaway_credits_status(
@ -508,7 +508,7 @@ void CreateGiveawayBox(
object_ptr<Giveaway::GiveawayTypeRow>( object_ptr<Giveaway::GiveawayTypeRow>(
box, box,
GiveawayType::Credits, GiveawayType::Credits,
kColorIndexCredits, st::colorIndexOrange,
tr::lng_credits_summary_title(), tr::lng_credits_summary_title(),
tr::lng_giveaway_create_subtitle(), tr::lng_giveaway_create_subtitle(),
std::move(badge))); std::move(badge)));

View file

@ -16,14 +16,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
#include "styles/style_color_indices.h"
#include "styles/style_giveaway.h" #include "styles/style_giveaway.h"
#include "styles/style_statistics.h" #include "styles/style_statistics.h"
namespace Giveaway { namespace Giveaway {
constexpr auto kColorIndexSpecific = int(4);
constexpr auto kColorIndexRandom = int(2);
GiveawayTypeRow::GiveawayTypeRow( GiveawayTypeRow::GiveawayTypeRow(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
Type type, Type type,
@ -32,7 +30,7 @@ GiveawayTypeRow::GiveawayTypeRow(
: GiveawayTypeRow( : GiveawayTypeRow(
parent, parent,
type, type,
(type == Type::SpecificUsers) ? kColorIndexSpecific : kColorIndexRandom, (type == Type::SpecificUsers) ? st::colorIndexBlue : st::colorIndexGreen,
(type == Type::SpecificUsers) (type == Type::SpecificUsers)
? tr::lng_giveaway_award_option() ? tr::lng_giveaway_award_option()
: (type == Type::Random) : (type == Type::Random)

View file

@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/slider_natural_width.h" #include "ui/widgets/slider_natural_width.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "styles/style_color_indices.h"
#include "styles/style_dialogs.h" // dialogsSearchTabs #include "styles/style_dialogs.h" // dialogsSearchTabs
#include "styles/style_giveaway.h" #include "styles/style_giveaway.h"
#include "styles/style_info.h" #include "styles/style_info.h"
@ -339,7 +340,6 @@ void InnerWidget::fill() {
Ui::AddSkip(inner); Ui::AddSkip(inner);
if (!status.prepaidGiveaway.empty()) { if (!status.prepaidGiveaway.empty()) {
constexpr auto kColorIndexCredits = int(1);
const auto multiplier = Api::PremiumGiftCodeOptions(_peer) const auto multiplier = Api::PremiumGiftCodeOptions(_peer)
.giveawayBoostsPerPremium(); .giveawayBoostsPerPremium();
Ui::AddSkip(inner); Ui::AddSkip(inner);
@ -352,7 +352,7 @@ void InnerWidget::fill() {
g.credits g.credits
? GiveawayTypeRow::Type::PrepaidCredits ? GiveawayTypeRow::Type::PrepaidCredits
: GiveawayTypeRow::Type::Prepaid, : GiveawayTypeRow::Type::Prepaid,
g.credits ? kColorIndexCredits : g.id, g.credits ? st::colorIndexOrange : g.id,
g.credits g.credits
? tr::lng_boosts_prepaid_giveaway_single() ? tr::lng_boosts_prepaid_giveaway_single()
: tr::lng_boosts_prepaid_giveaway_quantity( : tr::lng_boosts_prepaid_giveaway_quantity(

View file

@ -52,6 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/vertical_list.h" #include "ui/vertical_list.h"
#include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/input_field.h"
#include "ui/widgets/label_with_custom_emoji.h" #include "ui/widgets/label_with_custom_emoji.h"
#include "ui/widgets/peer_bubble.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/widgets/slider_natural_width.h" #include "ui/widgets/slider_natural_width.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
@ -1199,58 +1200,10 @@ void InnerWidget::fill() {
AddRecipient(box, recipient); AddRecipient(box, recipient);
} }
if (isIn) { if (isIn) {
const auto peerBubble = box->addRow( box->addRow(
object_ptr<Ui::CenterWrap<>>( object_ptr<Ui::CenterWrap<>>(
box, box,
object_ptr<Ui::RpWidget>(box)))->entity(); Ui::CreatePeerBubble(box, peer)));
peerBubble->setAttribute(
Qt::WA_TransparentForMouseEvents);
const auto left = Ui::CreateChild<Ui::UserpicButton>(
peerBubble,
peer,
st::uploadUserpicButton);
const auto right = Ui::CreateChild<Ui::FlatLabel>(
peerBubble,
Info::Profile::NameValue(peer),
st::channelEarnSemiboldLabel);
rpl::combine(
left->sizeValue(),
right->sizeValue()
) | rpl::start_with_next([=](
const QSize &leftSize,
const QSize &rightSize) {
const auto padding = QMargins(
st::chatGiveawayPeerPadding.left() * 2,
st::chatGiveawayPeerPadding.top(),
st::chatGiveawayPeerPadding.right(),
st::chatGiveawayPeerPadding.bottom());
peerBubble->resize(
leftSize.width()
+ rightSize.width()
+ rect::m::sum::h(padding),
leftSize.height());
left->moveToLeft(0, 0);
right->moveToRight(
padding.right(),
padding.top());
const auto maxRightSize = box->width()
- rect::m::sum::h(st::boxRowPadding)
- rect::m::sum::h(padding)
- leftSize.width();
if (rightSize.width() > maxRightSize) {
right->resizeToWidth(maxRightSize);
}
}, peerBubble->lifetime());
peerBubble->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(peerBubble);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::windowBgOver);
const auto rect = peerBubble->rect();
const auto radius = rect.height() / 2;
p.drawRoundedRect(rect, radius, radius);
}, peerBubble->lifetime());
} }
const auto closeBox = [=] { box->closeBox(); }; const auto closeBox = [=] { box->closeBox(); };
{ {

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_credits.h" #include "api/api_credits.h"
#include "api/api_statistics.h" #include "api/api_statistics.h"
#include "boxes/peer_list_controllers.h" #include "boxes/peer_list_controllers.h"
#include "boxes/peer_list_widgets.h"
#include "chat_helpers/stickers_gift_box_pack.h" #include "chat_helpers/stickers_gift_box_pack.h"
#include "core/ui_integration.h" // Core::MarkedTextContext. #include "core/ui_integration.h" // Core::MarkedTextContext.
#include "data/data_channel.h" #include "data/data_channel.h"
@ -24,6 +25,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h" #include "main/main_session.h"
#include "main/session/session_show.h" #include "main/session/session_show.h"
#include "settings/settings_credits_graphics.h" // PaintSubscriptionRightLabelCallback #include "settings/settings_credits_graphics.h" // PaintSubscriptionRightLabelCallback
#include "ui/dynamic_image.h"
#include "ui/dynamic_thumbnails.h"
#include "ui/effects/credits_graphics.h" #include "ui/effects/credits_graphics.h"
#include "ui/effects/outline_segments.h" // Ui::UnreadStoryOutlineGradient. #include "ui/effects/outline_segments.h" // Ui::UnreadStoryOutlineGradient.
#include "ui/effects/toggle_arrow.h" #include "ui/effects/toggle_arrow.h"
@ -35,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_color_indices.h"
#include "styles/style_credits.h" #include "styles/style_credits.h"
#include "styles/style_dialogs.h" // dialogsStoriesFull. #include "styles/style_dialogs.h" // dialogsStoriesFull.
#include "styles/style_layers.h" // boxRowPadding. #include "styles/style_layers.h" // boxRowPadding.
@ -47,9 +51,6 @@ namespace Info::Statistics {
namespace { namespace {
using BoostCallback = Fn<void(const Data::Boost &)>; using BoostCallback = Fn<void(const Data::Boost &)>;
constexpr auto kColorIndexCredits = int(1);
constexpr auto kColorIndexUnclaimed = int(3);
constexpr auto kColorIndexPending = int(4);
[[nodiscard]] PeerListRowId UniqueRowIdFromEntry( [[nodiscard]] PeerListRowId UniqueRowIdFromEntry(
const Data::CreditsHistoryEntry &entry) { const Data::CreditsHistoryEntry &entry) {
@ -70,6 +71,17 @@ void AddSubtitle(
{ 0, -subtitlePadding.top(), 0, -subtitlePadding.bottom() }); { 0, -subtitlePadding.top(), 0, -subtitlePadding.bottom() });
} }
[[nodiscard]] object_ptr<Ui::SettingsButton> CreateShowMoreButton(
not_null<Ui::RpWidget*> parent,
rpl::producer<QString> title) {
auto owned = object_ptr<Ui::SettingsButton>(
parent,
std::move(title),
st::statisticsShowMoreButton);
Ui::AddToggleUpDownArrowToMoreButton(owned.data());
return owned;
}
[[nodiscard]] QString FormatText( [[nodiscard]] QString FormatText(
int value1, tr::phrase<lngtag_count> phrase1, int value1, tr::phrase<lngtag_count> phrase1,
int value2, tr::phrase<lngtag_count> phrase2, int value2, tr::phrase<lngtag_count> phrase2,
@ -475,10 +487,10 @@ BoostRow::BoostRow(const Data::Boost &boost)
, _boost(boost) , _boost(boost)
, _userpic( , _userpic(
Ui::EmptyUserpic::UserpicColor(boost.credits Ui::EmptyUserpic::UserpicColor(boost.credits
? kColorIndexCredits ? st::colorIndexOrange
: boost.isUnclaimed : boost.isUnclaimed
? kColorIndexUnclaimed ? st::colorIndexSea
: kColorIndexPending), : st::colorIndexBlue),
QString()) { QString()) {
init(); init();
} }
@ -763,6 +775,18 @@ public:
bool selected, bool selected,
bool actionSelected) override; bool actionSelected) override;
void paintStatusText(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int available,
int outer,
bool selected) override;
const style::PeerListItem &computeSt(
const style::PeerListItem &st) const override;
private: private:
void init(); void init();
@ -776,8 +800,12 @@ private:
QString _title; QString _title;
QString _name; QString _name;
Ui::Text::String _description;
Ui::Text::String _rightText; Ui::Text::String _rightText;
std::shared_ptr<Ui::DynamicImage> _descriptionThumbnail;
QImage _descriptionThumbnailCache;
base::has_weak_ptr _guard; base::has_weak_ptr _guard;
}; };
@ -822,33 +850,37 @@ void CreditsRow::init() {
const auto name = !isSpecial const auto name = !isSpecial
? PeerListRow::generateName() ? PeerListRow::generateName()
: Ui::GenerateEntryName(_entry).text; : Ui::GenerateEntryName(_entry).text;
_name = (_entry.reaction || _entry.stargift || _entry.bareGiveawayMsgId) _name = _entry.title.isEmpty()
? Ui::GenerateEntryName(_entry).text ? name
: _entry.title.isEmpty() : (!_entry.subscriptionUntil.isNull() && !isSpecial)
? name ? name
: _entry.title; : _entry.title;
const auto joiner = QString(QChar(' ')) + QChar(8212) + QChar(' ');
setSkipPeerBadge(true); setSkipPeerBadge(true);
PeerListRow::setCustomStatus( const auto description = _entry.floodSkip
langDateTime(_entry.date) ? tr::lng_credits_box_history_entry_floodskip_about(
+ (_entry.floodSkip tr::now,
? (joiner + tr::lng_credits_box_history_entry_floodskip_about( lt_count_decimal,
tr::now, _entry.floodSkip)
lt_count_decimal, : (!_entry.subscriptionUntil.isNull() && !_entry.title.isEmpty())
_entry.floodSkip)) ? _entry.title
: _entry.refunded : _entry.refunded
? (joiner + tr::lng_channel_earn_history_return(tr::now)) ? tr::lng_channel_earn_history_return(tr::now)
: _entry.pending : _entry.pending
? (joiner + tr::lng_channel_earn_history_pending(tr::now)) ? tr::lng_channel_earn_history_pending(tr::now)
: _entry.failed : _entry.failed
? (joiner + tr::lng_channel_earn_history_failed(tr::now)) ? tr::lng_channel_earn_history_failed(tr::now)
: !_entry.subscriptionUntil.isNull() : !_entry.subscriptionUntil.isNull()
? (joiner ? tr::lng_credits_box_history_entry_subscription(tr::now)
+ tr::lng_credits_box_history_entry_subscription(tr::now)) : (_entry.peerType
: QString()) == Data::CreditsHistoryEntry::PeerType::PremiumBot)
+ ((_entry.gift && isSpecial) ? tr::lng_credits_box_history_entry_via_premium_bot(tr::now)
? (joiner + tr::lng_credits_box_history_entry_anonymous(tr::now)) : (_entry.gift && isSpecial)
: ((_name == name) ? QString() : (joiner + name)))); ? tr::lng_credits_box_history_entry_anonymous(tr::now)
: (_name == name)
? Ui::GenerateEntryName(_entry).text
: name;
_description.setText(st::defaultTextStyle, description);
PeerListRow::setCustomStatus(langDateTime(_entry.date));
if (_subscription) { if (_subscription) {
PeerListRow::setCustomStatus((_subscription.expired PeerListRow::setCustomStatus((_subscription.expired
? tr::lng_credits_subscription_status_none ? tr::lng_credits_subscription_status_none
@ -858,6 +890,24 @@ void CreditsRow::init() {
tr::now, tr::now,
lt_date, lt_date,
langDayOfMonthFull(_subscription.until.date()))); langDayOfMonthFull(_subscription.until.date())));
_description.setText(st::defaultTextStyle, _subscription.title);
}
const auto descriptionPhotoId = (!_entry.subscriptionUntil.isNull())
? _entry.photoId
: _subscription.photoId;
if (descriptionPhotoId) {
_descriptionThumbnail = Ui::MakePhotoThumbnail(
_context.session->data().photo(descriptionPhotoId),
{});
_descriptionThumbnail->subscribeToUpdates([this] {
const auto thumbnailSide = st::defaultTextStyle.font->height;
_descriptionThumbnailCache = Images::Round(
_descriptionThumbnail->image(thumbnailSide),
ImageRoundRadius::Large);
if (_context.customEmojiRepaint) {
_context.customEmojiRepaint();
}
});
} }
auto &manager = _context.session->data().customEmojiManager(); auto &manager = _context.session->data().customEmojiManager();
if (_entry) { if (_entry) {
@ -894,23 +944,40 @@ const Data::SubscriptionEntry &CreditsRow::subscription() const {
} }
QString CreditsRow::generateName() { QString CreditsRow::generateName() {
return _entry.title.isEmpty() ? _name : _entry.title; return (!_entry.title.isEmpty() && !_entry.subscriptionUntil.isNull())
? _name
: _entry.title.isEmpty()
? _name
: _entry.title;
} }
PaintRoundImageCallback CreditsRow::generatePaintUserpicCallback(bool force) { PaintRoundImageCallback CreditsRow::generatePaintUserpicCallback(bool force) {
return _paintUserpicCallback; return _paintUserpicCallback;
} }
[[nodiscard]] QString RightActionText(const Data::SubscriptionEntry &s) {
return s.cancelledByBot
? tr::lng_credits_subscription_status_off_by_bot_right(tr::now)
: s.cancelled
? tr::lng_credits_subscription_status_off_right(tr::now)
: s.expired
? tr::lng_credits_subscription_status_none_right(tr::now)
: QString();
}
QSize CreditsRow::rightActionSize() const { QSize CreditsRow::rightActionSize() const {
if (_rightLabel) { if (_rightLabel) {
return _rightLabel->size; return _rightLabel->size;
} else if (_subscription.cancelled || _subscription.expired) { } else if (const auto t = RightActionText(_subscription); !t.isEmpty()) {
const auto text = _subscription.cancelled const auto lines = t.split('\n');
? tr::lng_credits_subscription_status_off_right(tr::now) auto maxWidth = 0;
: tr::lng_credits_subscription_status_none_right(tr::now); for (const auto &line : lines) {
return QSize( const auto width = st::contactsStatusFont->width(line);
st::contactsStatusFont->width(text) + st::boxRowPadding.right(), if (width > maxWidth) {
_rowHeight); maxWidth = width;
}
}
return QSize(maxWidth + st::boxRowPadding.right(), _rowHeight);
} else if (_subscription || _entry) { } else if (_subscription || _entry) {
return QSize( return QSize(
_rightText.maxWidth() + st::boxRowPadding.right() / 2, _rightText.maxWidth() + st::boxRowPadding.right() / 2,
@ -940,18 +1007,31 @@ void CreditsRow::rightActionPaint(
const auto rightSkip = st::boxRowPadding.right(); const auto rightSkip = st::boxRowPadding.right();
if (_rightLabel) { if (_rightLabel) {
return _rightLabel->draw(p, x, y, _rowHeight); return _rightLabel->draw(p, x, y, _rowHeight);
} else if (_subscription.cancelled || _subscription.expired) { } else if (const auto t = RightActionText(_subscription); !t.isEmpty()) {
const auto &statusFont = st::contactsStatusFont; const auto &statusFont = st::contactsStatusFont;
y += _rowHeight / 2; y += _rowHeight / 2;
p.setFont(statusFont); p.setFont(statusFont);
p.setPen(st::attentionButtonFg); p.setPen(st::attentionButtonFg);
p.drawTextRight(
rightSkip, const auto lines = t.split('\n');
y - statusFont->height / 2, if (lines.size() > 1) {
outerWidth, const auto rect = QRect(x, 0, outerWidth - x, _rowHeight);
_subscription.expired const auto lineHeight = statusFont->height;
? tr::lng_credits_subscription_status_none_right(tr::now) const auto totalHeight = lines.size() * lineHeight;
: tr::lng_credits_subscription_status_off_right(tr::now)); auto startY = rect.top()
+ (rect.height() - totalHeight) / 2
+ statusFont->ascent;
for (const auto &line : lines) {
const auto lineWidth = statusFont->width(line);
const auto startX = rect.left()
+ (rect.width() - lineWidth) / 2;
p.drawText(startX, startY, line);
startY += lineHeight;
}
return;
}
p.drawTextRight(rightSkip, y - statusFont->height / 2, outerWidth, t);
return; return;
} }
y += _rowHeight / 2; y += _rowHeight / 2;
@ -969,6 +1049,43 @@ void CreditsRow::rightActionPaint(
}); });
} }
void CreditsRow::paintStatusText(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int available,
int outer,
bool selected) {
PeerListRow::paintStatusText(p, st, x, y, available, outer, selected);
p.setPen(st.nameFg);
if (!_descriptionThumbnailCache.isNull()) {
const auto thumbnailSide = _descriptionThumbnailCache.width()
/ style::DevicePixelRatio();
const auto thumbnailSpace = st::lineWidth * 4 + thumbnailSide;
p.drawImage(
x,
y - thumbnailSide,
_descriptionThumbnailCache);
x += thumbnailSpace;
outer -= thumbnailSpace;
available -= thumbnailSpace;
}
_description.draw(p, {
.position = QPoint(x, y - _description.minHeight()),
.outerWidth = outer,
.availableWidth = available,
.elisionLines = 1,
});
}
const style::PeerListItem &CreditsRow::computeSt(
const style::PeerListItem &st) const {
return (!_subscription || !_subscription.title.isEmpty())
? st
: st::boostsListBox.item;
}
class CreditsController final : public PeerListController { class CreditsController final : public PeerListController {
public: public:
explicit CreditsController(CreditsDescriptor d); explicit CreditsController(CreditsDescriptor d);
@ -1010,7 +1127,7 @@ CreditsController::CreditsController(CreditsDescriptor d)
.session = _session, .session = _session,
.customEmojiRepaint = [] {}, .customEmojiRepaint = [] {},
}) { }) {
PeerListController::setStyleOverrides(&st::boostsListBox); PeerListController::setStyleOverrides(&st::creditsHistoryEntriesList);
} }
Main::Session &CreditsController::session() const { Main::Session &CreditsController::session() const {
@ -1052,7 +1169,9 @@ void CreditsController::applySlice(const Data::CreditsStatusSlice &slice) {
.entry = i, .entry = i,
.subscription = s, .subscription = s,
.context = _context, .context = _context,
.rowHeight = computeListSt().item.height, .rowHeight = ((!s || !s.title.isEmpty())
? computeListSt().item
: st::boostsListBox.item).height,
.updateCallback = [=](not_null<PeerListRow*> row) { .updateCallback = [=](not_null<PeerListRow*> row) {
delegate()->peerListUpdateRow(row); delegate()->peerListUpdateRow(row);
}, },
@ -1239,28 +1358,36 @@ void AddCreditsHistoryList(
not_null<PeerData*> bot, not_null<PeerData*> bot,
bool in, bool in,
bool out, bool out,
bool subscription) { bool subs) {
struct State final { struct State final {
State( State(CreditsDescriptor d) : controller(std::move(d)) {
CreditsDescriptor d,
std::shared_ptr<Main::SessionShow> show)
: delegate(std::move(show))
, controller(std::move(d)) {
} }
PeerListContentDelegateShow delegate; std::optional<PeerListContentDelegateShow> creditsDelegate;
std::optional<PeerListWidgetsDelegate> subscriptionDelegate;
CreditsController controller; CreditsController controller;
}; };
const auto state = container->lifetime().make_state<State>( const auto state = container->lifetime().make_state<State>(
CreditsDescriptor{ firstSlice, callback, bot, in, out, subscription }, CreditsDescriptor{ firstSlice, callback, bot, in, out, subs });
show); if (subs) {
state->subscriptionDelegate.emplace();
state->subscriptionDelegate->setUiShow(show);
state->subscriptionDelegate->setContent(container->add(
object_ptr<PeerListWidgets>(container, &state->controller)));
state->controller.setDelegate(&(*state->subscriptionDelegate));
} else {
state->creditsDelegate.emplace(show);
state->creditsDelegate->setContent(container->add(
object_ptr<PeerListContent>(container, &state->controller)));
state->controller.setDelegate(&(*state->creditsDelegate));
}
state->delegate.setContent(container->add( const auto wrap = container->add(
object_ptr<PeerListContent>(container, &state->controller))); object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
state->controller.setDelegate(&state->delegate); container,
CreateShowMoreButton(container, tr::lng_stories_show_more())),
const auto wrap = AddShowMoreButton( subs
container, ? QMargins()
tr::lng_stories_show_more()); : QMargins(0, -st::settingsButton.padding.top(), 0, 0));
const auto showMore = [=] { const auto showMore = [=] {
if (!state->controller.skipRequest()) { if (!state->controller.skipRequest()) {
@ -1277,16 +1404,11 @@ void AddCreditsHistoryList(
not_null<Ui::SlideWrap<Ui::SettingsButton>*> AddShowMoreButton( not_null<Ui::SlideWrap<Ui::SettingsButton>*> AddShowMoreButton(
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
rpl::producer<QString> title) { rpl::producer<QString> title) {
const auto wrap = container->add( return container->add(
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>( object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
container, container,
object_ptr<Ui::SettingsButton>( CreateShowMoreButton(container, std::move(title))),
container,
std::move(title),
st::statisticsShowMoreButton)),
{ 0, -st::settingsButton.padding.top(), 0, 0 }); { 0, -st::settingsButton.padding.top(), 0, 0 });
Ui::AddToggleUpDownArrowToMoreButton(wrap->entity());
return wrap;
} }
} // namespace Info::Statistics } // namespace Info::Statistics

View file

@ -643,12 +643,13 @@ void ConfirmEmojiStatusBox(
done(true); done(true);
}); });
box->addButton(tr::lng_cancel(), [=] { box->addButton(tr::lng_cancel(), [=] {
const auto was = *set;
box->closeBox(); box->closeBox();
if (!was) { });
box->boxClosing() | rpl::start_with_next([=] {
if (!*set) {
done(false); done(false);
} }
}); }, box->lifetime());
} }
class BotAction final : public Ui::Menu::ItemBase { class BotAction final : public Ui::Menu::ItemBase {
@ -954,7 +955,12 @@ void WebViewInstance::resolve() {
requestSimple(); requestSimple();
}); });
}, [&](WebViewSourceLinkApp data) { }, [&](WebViewSourceLinkApp data) {
resolveApp(data.appname, data.token, !_context.maySkipConfirmation); resolveApp(
data.appname,
data.token,
(_context.maySkipConfirmation
? ConfirmType::None
: ConfirmType::Always));
}, [&](WebViewSourceLinkBotProfile) { }, [&](WebViewSourceLinkBotProfile) {
confirmOpen([=] { confirmOpen([=] {
requestMain(); requestMain();
@ -1002,14 +1008,14 @@ bool WebViewInstance::openAppFromBotMenuLink() {
if (appname.isEmpty()) { if (appname.isEmpty()) {
return false; return false;
} }
resolveApp(appname, params.value(u"startapp"_q), true); resolveApp(appname, params.value(u"startapp"_q), ConfirmType::Once);
return true; return true;
} }
void WebViewInstance::resolveApp( void WebViewInstance::resolveApp(
const QString &appname, const QString &appname,
const QString &startparam, const QString &startparam,
bool forceConfirmation) { ConfirmType confirmType) {
const auto already = _session->data().findBotApp(_bot->id, appname); const auto already = _session->data().findBotApp(_bot->id, appname);
_requestId = _session->api().request(MTPmessages_GetBotApp( _requestId = _session->api().request(MTPmessages_GetBotApp(
MTP_inputBotAppShortName( MTP_inputBotAppShortName(
@ -1029,8 +1035,11 @@ void WebViewInstance::resolveApp(
close(); close();
return; return;
} }
const auto confirm = data.is_inactive() || forceConfirmation; const auto confirm = data.is_inactive()
|| (confirmType != ConfirmType::None);
const auto writeAccess = result.data().is_request_write_access(); const auto writeAccess = result.data().is_request_write_access();
const auto forceConfirmation = data.is_inactive()
|| (confirmType == ConfirmType::Always);
// Check if this app can be added to main menu. // Check if this app can be added to main menu.
// On fail it'll still be opened. // On fail it'll still be opened.
@ -1043,7 +1052,7 @@ void WebViewInstance::resolveApp(
} else if (confirm) { } else if (confirm) {
confirmAppOpen(writeAccess, [=](bool allowWrite) { confirmAppOpen(writeAccess, [=](bool allowWrite) {
requestApp(allowWrite); requestApp(allowWrite);
}); }, forceConfirmation);
} else { } else {
requestApp(false); requestApp(false);
} }
@ -1090,10 +1099,18 @@ void WebViewInstance::confirmOpen(Fn<void()> done) {
void WebViewInstance::confirmAppOpen( void WebViewInstance::confirmAppOpen(
bool writeAccess, bool writeAccess,
Fn<void(bool allowWrite)> done) { Fn<void(bool allowWrite)> done,
bool forceConfirmation) {
if (!forceConfirmation
&& (_bot->isVerified()
|| _session->local().isBotTrustedOpenWebView(_bot->id))) {
done(writeAccess);
return;
}
_parentShow->show(Box([=](not_null<Ui::GenericBox*> box) { _parentShow->show(Box([=](not_null<Ui::GenericBox*> box) {
const auto allowed = std::make_shared<Ui::Checkbox*>(); const auto allowed = std::make_shared<Ui::Checkbox*>();
const auto callback = [=](Fn<void()> close) { const auto callback = [=](Fn<void()> close) {
_session->local().markBotTrustedOpenWebView(_bot->id);
done((*allowed) && (*allowed)->checked()); done((*allowed) && (*allowed)->checked());
close(); close();
}; };

View file

@ -230,12 +230,20 @@ private:
void requestWithMenuAdd(); void requestWithMenuAdd();
void maybeChooseAndRequestButton(PeerTypes supported); void maybeChooseAndRequestButton(PeerTypes supported);
enum class ConfirmType : uchar {
Always,
Once,
None,
};
void resolveApp( void resolveApp(
const QString &appname, const QString &appname,
const QString &startparam, const QString &startparam,
bool forceConfirmation); ConfirmType confirmType);
void confirmOpen(Fn<void()> done); void confirmOpen(Fn<void()> done);
void confirmAppOpen(bool writeAccess, Fn<void(bool allowWrite)> done); void confirmAppOpen(
bool writeAccess,
Fn<void(bool allowWrite)> done,
bool forceConfirmation);
struct ShowArgs { struct ShowArgs {
QString url; QString url;

View file

@ -456,6 +456,8 @@ void Form::requestForm() {
const auto amount = tlPrices.empty() const auto amount = tlPrices.empty()
? 0 ? 0
: tlPrices.front().data().vamount().v; : tlPrices.front().data().vamount().v;
const auto subscriptionPeriod
= data.vinvoice().data().vsubscription_period().value_or(0);
if (currency != ::Ui::kCreditsCurrency || !amount) { if (currency != ::Ui::kCreditsCurrency || !amount) {
using Type = Error::Type; using Type = Error::Type;
_updates.fire(Error{ Type::Form, u"Bad Stars Form."_q }); _updates.fire(Error{ Type::Form, u"Bad Stars Form."_q });
@ -467,6 +469,7 @@ void Form::requestForm() {
.credits = amount, .credits = amount,
.currency = currency, .currency = currency,
.amount = amount, .amount = amount,
.subscriptionPeriod = subscriptionPeriod,
}; };
const auto formData = CreditsFormData{ const auto formData = CreditsFormData{
.id = _id, .id = _id,

View file

@ -170,6 +170,7 @@ struct InvoiceCredits {
uint64 amount = 0; uint64 amount = 0;
bool extended = false; bool extended = false;
PeerId giftPeerId = PeerId(0); PeerId giftPeerId = PeerId(0);
int subscriptionPeriod = 0;
}; };
struct InvoiceStarGift { struct InvoiceStarGift {

View file

@ -94,14 +94,14 @@ void ProcessCreditsPayment(
Ui::SendCreditsBox, Ui::SendCreditsBox,
form, form,
[=] { [=] {
*unsuccessful = false; *unsuccessful = false;
if (const auto widget = fireworks.data()) { if (const auto widget = fireworks.data()) {
Ui::StartFireworks(widget); Ui::StartFireworks(widget);
} }
if (const auto onstack = maybeReturnToBot) { if (const auto onstack = maybeReturnToBot) {
onstack(CheckoutResult::Paid); onstack(CheckoutResult::Paid);
} }
})); }));
box->boxClosing() | rpl::start_with_next([=] { box->boxClosing() | rpl::start_with_next([=] {
crl::on_main([=] { crl::on_main([=] {
if (*unsuccessful) { if (*unsuccessful) {

View file

@ -191,6 +191,19 @@ void Credits::setupSubscriptions(not_null<Ui::VerticalLayout*> container) {
fill(std::move(d)); fill(std::move(d));
}); });
} }
{
using Rebuilder = Data::Session::CreditsSubsRebuilder;
using RebuilderPtr = std::shared_ptr<Rebuilder>;
const auto rebuilder = content->lifetime().make_state<RebuilderPtr>(
self->owner().createCreditsSubsRebuilder());
rebuilder->get()->events(
) | rpl::start_with_next([=](Data::CreditsStatusSlice slice) {
while (content->count()) {
delete content->widgetAt(0);
}
fill(std::move(slice));
}, content->lifetime());
}
} }
void Credits::setupHistory(not_null<Ui::VerticalLayout*> container) { void Credits::setupHistory(not_null<Ui::VerticalLayout*> container) {

View file

@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h" #include "main/main_session.h"
#include "payments/payments_checkout_process.h" #include "payments/payments_checkout_process.h"
#include "payments/payments_form.h" #include "payments/payments_form.h"
#include "payments/payments_non_panel_process.h"
#include "settings/settings_common_session.h" #include "settings/settings_common_session.h"
#include "settings/settings_credits.h" #include "settings/settings_credits.h"
#include "statistics/widgets/chart_header_widget.h" #include "statistics/widgets/chart_header_widget.h"
@ -346,6 +347,37 @@ void AddViewMediaHandler(
}, thumb->lifetime()); }, thumb->lifetime());
} }
void AddMiniStars(
not_null<Ui::VerticalLayout*> content,
not_null<Ui::RpWidget*> widget,
const style::UserpicButton &stUser,
int boxWidth,
float64 heightRatio) {
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(
boxWidth - stUser.photoSize,
stUser.photoSize * heightRatio);
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);
}, widget->lifetime());
}
} // namespace } // namespace
SubscriptionRightLabel PaintSubscriptionRightLabelCallback( SubscriptionRightLabel PaintSubscriptionRightLabelCallback(
@ -664,31 +696,12 @@ void BoostCreditsBox(
{ {
const auto &stUser = st::premiumGiftsUserpicButton; const auto &stUser = st::premiumGiftsUserpicButton;
const auto widget = content->add(object_ptr<Ui::RpWidget>(content)); const auto widget = content->add(object_ptr<Ui::RpWidget>(content));
using ColoredMiniStars = Ui::Premium::ColoredMiniStars; AddMiniStars(content, widget, stUser, st::boxWidth, 1.3);
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>( const auto svg = std::make_shared<QSvgRenderer>(
Ui::Premium::ColorizedSvg( Ui::Premium::ColorizedSvg(
Ui::Premium::CreditsIconGradientStops())); Ui::Premium::CreditsIconGradientStops()));
content->sizeValue( widget->paintRequest() | rpl::start_with_next([=](const QRect &r) {
) | 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); auto p = QPainter(widget);
p.fillRect(r, Qt::transparent);
stars->paint(p);
svg->render( svg->render(
&p, &p,
QRectF( QRectF(
@ -790,6 +803,28 @@ void BoostCreditsBox(
}, button->lifetime()); }, button->lifetime());
} }
void ProcessReceivedSubscriptions(
QPointer<Ui::GenericBox> weak,
not_null<Main::Session*> session) {
const auto rebuilder = session->data().activeCreditsSubsRebuilder();
if (const auto strong = weak.data()) {
if (!rebuilder) {
return strong->closeBox();
}
const auto api
= strong->lifetime().make_state<Api::CreditsHistory>(
session->user(),
true,
true);
api->requestSubscriptions({}, [=](Data::CreditsStatusSlice first) {
rebuilder->fire(std::move(first));
if (const auto strong = weak.data()) {
strong->closeBox();
}
});
}
}
void ReceiptCreditsBox( void ReceiptCreditsBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
@ -820,6 +855,7 @@ void ReceiptCreditsBox(
const auto nonConvertible = (gotStarGift && !e.starsConverted); const auto nonConvertible = (gotStarGift && !e.starsConverted);
box->setStyle(st::giveawayGiftCodeBox); box->setStyle(st::giveawayGiftCodeBox);
box->setWidth(st::boxWideWidth);
box->setNoContentMargin(true); box->setNoContentMargin(true);
const auto content = box->verticalLayout(); const auto content = box->verticalLayout();
@ -851,6 +887,20 @@ void ReceiptCreditsBox(
content, content,
GenericEntryPhoto(content, callback, stUser.photoSize))); GenericEntryPhoto(content, callback, stUser.photoSize)));
AddViewMediaHandler(thumb->entity(), controller, e); AddViewMediaHandler(thumb->entity(), controller, e);
} else if (s.photoId || (e.photoId && !e.subscriptionUntil.isNull())) {
if (!(s.cancelled || s.expired || s.cancelledByBot)) {
const auto widget = Ui::CreateChild<Ui::RpWidget>(content);
AddMiniStars(content, widget, stUser, st::boxWideWidth, 1.5);
}
const auto photoId = s.photoId ? s.photoId : e.photoId;
const auto callback = [=](Fn<void()> update) {
return Ui::GenerateCreditsPaintEntryCallback(
session->data().photo(photoId),
std::move(update));
};
content->add(object_ptr<Ui::CenterWrap<>>(
content,
GenericEntryPhoto(content, callback, stUser.photoSize)));
} else if (peer && !e.gift) { } else if (peer && !e.gift) {
if (e.subscriptionUntil.isNull() && s.until.isNull()) { if (e.subscriptionUntil.isNull() && s.until.isNull()) {
content->add(object_ptr<Ui::CenterWrap<>>( content->add(object_ptr<Ui::CenterWrap<>>(
@ -950,11 +1000,13 @@ void ReceiptCreditsBox(
box, box,
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
box, box,
rpl::single(!s.until.isNull() rpl::single(!s.title.isEmpty()
? s.title
: !s.until.isNull()
? tr::lng_credits_box_subscription_title(tr::now) ? tr::lng_credits_box_subscription_title(tr::now)
: isPrize : isPrize
? tr::lng_credits_box_history_entry_giveaway_name(tr::now) ? tr::lng_credits_box_history_entry_giveaway_name(tr::now)
: !e.subscriptionUntil.isNull() : (!e.subscriptionUntil.isNull() && e.title.isEmpty())
? tr::lng_credits_box_history_entry_subscription(tr::now) ? tr::lng_credits_box_history_entry_subscription(tr::now)
: !e.title.isEmpty() : !e.title.isEmpty()
? e.title ? e.title
@ -1270,16 +1322,48 @@ void ReceiptCreditsBox(
st::creditsBoxAboutDivider))); st::creditsBoxAboutDivider)));
} }
if (s) { if (s) {
const auto user = peer ? peer->asUser() : nullptr;
const auto bot = (user && !user->isSelf()) ? user : nullptr;
const auto toCancel = !s.expired && !s.cancelled && !s.cancelledByBot;
if (toCancel) {
Ui::AddSkip(content);
}
Ui::AddSkip(content); Ui::AddSkip(content);
auto label = object_ptr<Ui::FlatLabel>( auto label = object_ptr<Ui::FlatLabel>(
box, box,
s.cancelled (s.cancelledByBot && bot)
? tr::lng_credits_subscription_off_by_bot_about(
lt_bot,
rpl::single(bot->name()))
: toCancel
? tr::lng_credits_subscription_on_button()
: s.cancelled
? tr::lng_credits_subscription_off_about() ? tr::lng_credits_subscription_off_about()
: tr::lng_credits_subscription_on_about( : tr::lng_credits_subscription_on_about(
lt_date, lt_date,
rpl::single(langDayOfMonthFull(s.until.date()))), rpl::single(langDayOfMonthFull(s.until.date()))),
st::creditsBoxAboutDivider); st::creditsBoxAboutDivider);
if (s.cancelled) { if (toCancel) {
label->setClickHandlerFilter([=](
const auto &,
Qt::MouseButton button) {
if (button != Qt::LeftButton) {
return false;
}
const auto done = [=, weak = Ui::MakeWeak(box)] {
ProcessReceivedSubscriptions(weak, session);
};
const auto fail = [=, s = box->uiShow()](const QString &e) {
s->showToast(e);
};
Api::EditCreditsSubscription(session, s.id, true, done, fail);
return true;
});
label->setMarkedText(
Ui::Text::Link(
tr::lng_credits_subscription_on_button(tr::now),
u"internal:"_q));
} else if (s.cancelled || s.cancelledByBot) {
label->setTextColorOverride(st::menuIconAttentionColor->c); label->setTextColorOverride(st::menuIconAttentionColor->c);
} }
box->addRow( box->addRow(
@ -1290,41 +1374,26 @@ void ReceiptCreditsBox(
if (e.peerType == Data::CreditsHistoryEntry::PeerType::PremiumBot) { if (e.peerType == Data::CreditsHistoryEntry::PeerType::PremiumBot) {
const auto widget = Ui::CreateChild<Ui::RpWidget>(content); const auto widget = Ui::CreateChild<Ui::RpWidget>(content);
using ColoredMiniStars = Ui::Premium::ColoredMiniStars; AddMiniStars(content, widget, stUser, st::boxWideWidth, 2);
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 * 2);
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);
}, widget->lifetime());
} }
const auto rejoinByApi = base::unixtime::serialize(s.until)
> base::unixtime::now();
const auto rejoinByInvite = !s.inviteHash.isEmpty();
const auto rejoinBySlug = !s.slug.isEmpty();
const auto toRenew = (s.cancelled || s.expired) const auto toRenew = (s.cancelled || s.expired)
&& !s.inviteHash.isEmpty(); && (rejoinByApi || rejoinByInvite)
const auto toCancel = !toRenew && s; && !s.cancelledByBot;
const auto toRejoin = (s.cancelled || s.expired)
&& rejoinBySlug
&& !s.cancelledByBot;
auto confirmText = rpl::conditional( auto confirmText = rpl::conditional(
state->confirmButtonBusy.value(), state->confirmButtonBusy.value(),
rpl::single(QString()), rpl::single(QString()),
(toRenew (toRenew
? tr::lng_credits_subscription_off_button() ? tr::lng_credits_subscription_off_button()
: toCancel : toRejoin
? tr::lng_credits_subscription_on_button() ? tr::lng_credits_subscription_off_rejoin_button()
: (canConvert || couldConvert || nonConvertible) : (canConvert || couldConvert || nonConvertible)
? (e.savedToProfile ? (e.savedToProfile
? tr::lng_gift_display_on_page_hide() ? tr::lng_gift_display_on_page_hide()
@ -1372,45 +1441,57 @@ void ReceiptCreditsBox(
save, save,
done); done);
} }
} else if (toRejoin) {
if (const auto window = weakWindow.get()) {
const auto finish = [=](Payments::CheckoutResult&&) {
ProcessReceivedSubscriptions(weak, session);
};
Payments::CheckoutProcess::Start(
&window->session(),
s.slug,
[](auto) {},
Payments::ProcessNonPanelPaymentFormFactory(
window,
finish));
}
} else if (toRenew && s.expired) { } else if (toRenew && s.expired) {
Api::CheckChatInvite(controller, s.inviteHash, nullptr, [=] { Api::CheckChatInvite(controller, s.inviteHash, nullptr, [=] {
if (const auto strong = weak.data()) { ProcessReceivedSubscriptions(weak, session);
strong->closeBox();
}
}); });
} else { } else {
using Flag = MTPpayments_ChangeStarsSubscription::Flag; const auto done = [=] {
session->api().request( ProcessReceivedSubscriptions(weak, session);
MTPpayments_ChangeStarsSubscription( };
MTP_flags(Flag::f_canceled), const auto fail = [=, show = box->uiShow()](const QString &e) {
MTP_inputPeerSelf(),
MTP_string(s.id),
MTP_bool(toCancel)
)).done([=] {
if (const auto strong = weak.data()) {
strong->closeBox();
}
}).fail([=, show = box->uiShow()](const MTP::Error &error) {
if (const auto strong = weak.data()) { if (const auto strong = weak.data()) {
state->confirmButtonBusy = false; state->confirmButtonBusy = false;
} }
show->showToast(error.type()); show->showToast(e);
}).send(); };
Api::EditCreditsSubscription(session, s.id, false, done, fail);
} }
}; };
const auto willBusy = toRejoin
|| (peer
&& (toRenew || canConvert || couldConvert || nonConvertible));
if (willBusy) {
const auto close = Ui::CreateChild<Ui::IconButton>(
content,
st::boxTitleClose);
close->setClickedCallback([=] { box->closeBox(); });
content->widthValue() | rpl::start_with_next([=](int) {
close->moveToRight(0, 0);
}, content->lifetime());
}
const auto button = box->addButton(std::move(confirmText), [=] { const auto button = box->addButton(std::move(confirmText), [=] {
if (state->confirmButtonBusy.current() if (state->confirmButtonBusy.current()
|| state->convertButtonBusy.current()) { || state->convertButtonBusy.current()) {
return; return;
} }
state->confirmButtonBusy = true; if (willBusy) {
if (peer state->confirmButtonBusy = true;
&& (toRenew
|| toCancel
|| canConvert
|| couldConvert
|| nonConvertible)) {
send(); send();
} else { } else {
box->closeBox(); box->closeBox();
@ -1424,7 +1505,7 @@ void ReceiptCreditsBox(
AddChildToWidgetCenter(button, loadingAnimation); AddChildToWidgetCenter(button, loadingAnimation);
loadingAnimation->showOn(state->confirmButtonBusy.value()); loadingAnimation->showOn(state->confirmButtonBusy.value());
} }
const auto buttonWidth = st::boxWidth const auto buttonWidth = st::boxWideWidth
- rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding); - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding);
button->widthValue() | rpl::filter([=] { button->widthValue() | rpl::filter([=] {

View file

@ -0,0 +1,16 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
colorIndexRed: 0;
colorIndexOrange: 1;
colorIndexGreen: 2;
colorIndexSea: 3;
colorIndexBlue: 4;
colorIndexPurple: 5;
colorIndexPink: 6;
colorIndexYellow: 7;

View file

@ -156,3 +156,20 @@ giftListAbout: FlatLabel(defaultFlatLabel) {
giftListAboutMargin: margins(12px, 24px, 12px, 24px); giftListAboutMargin: margins(12px, 24px, 12px, 24px);
giftBoxEmojiToggleTop: 7px; giftBoxEmojiToggleTop: 7px;
giftBoxLimitTop: 28px; giftBoxLimitTop: 28px;
creditsHistoryEntriesList: PeerList(defaultPeerList) {
padding: margins(
0px,
7px,
0px,
7px);
item: PeerListItem(defaultPeerListItem) {
height: 66px;
photoPosition: point(18px, 6px);
namePosition: point(70px, 6px);
statusPosition: point(70px, 43px);
photoSize: 42px;
}
}
subscriptionCreditsBadgePadding: margins(10px, 1px, 8px, 3px);

View file

@ -533,7 +533,7 @@ Fn<PaintRoundImageCallback(Fn<void()>)> PaintPreviewCallback(
extended, extended,
std::move(update)); std::move(update));
}; };
} else if (entry.photoId) { } else if (entry.photoId && entry.subscriptionUntil.isNull()) {
const auto photo = session->data().photo(entry.photoId); const auto photo = session->data().photo(entry.photoId);
return [=](Fn<void()> update) { return [=](Fn<void()> update) {
return GenerateCreditsPaintEntryCallback( return GenerateCreditsPaintEntryCallback(

View file

@ -57,6 +57,26 @@ ChatsFiltersTabs::ChatsFiltersTabs(
Ui::DiscreteSlider::setSelectOnPress(false); Ui::DiscreteSlider::setSelectOnPress(false);
} }
bool ChatsFiltersTabs::setSectionsAndCheckChanged(
std::vector<QString> &&sections) {
const auto &was = sectionsRef();
const auto changed = [&] {
if (was.size() != sections.size()) {
return true;
}
for (auto i = 0; i < sections.size(); i++) {
if (was[i].label.toString() != sections[i]) {
return true;
}
}
return false;
}();
if (changed) {
Ui::DiscreteSlider::setSections(std::move(sections));
}
return changed;
}
int ChatsFiltersTabs::centerOfSection(int section) const { int ChatsFiltersTabs::centerOfSection(int section) const {
const auto widths = countSectionsWidths(0); const auto widths = countSectionsWidths(0);
auto result = 0; auto result = 0;

View file

@ -27,6 +27,8 @@ public:
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
const style::SettingsSlider &st); const style::SettingsSlider &st);
bool setSectionsAndCheckChanged(std::vector<QString> &&sections);
[[nodiscard]] int centerOfSection(int section) const; [[nodiscard]] int centerOfSection(int section) const;
void fitWidthToSections(); void fitWidthToSections();
void setUnreadCount(int index, int unreadCount, bool muted); void setUnreadCount(int index, int unreadCount, bool muted);

View file

@ -293,15 +293,18 @@ not_null<Ui::RpWidget*> AddChatFiltersTabsStrip(
if ((list.size() <= 1 && !slider->width()) || state->ignoreRefresh) { if ((list.size() <= 1 && !slider->width()) || state->ignoreRefresh) {
return; return;
} }
const auto sectionsChanged = slider->setSectionsAndCheckChanged(
ranges::views::all(
list
) | ranges::views::transform([](const Data::ChatFilter &filter) {
return filter.title().isEmpty()
? tr::lng_filters_all_short(tr::now)
: filter.title();
}) | ranges::to_vector);
if (!sectionsChanged) {
return;
}
state->rebuildLifetime.destroy(); state->rebuildLifetime.destroy();
auto sections = ranges::views::all(
list
) | ranges::views::transform([](const Data::ChatFilter &filter) {
return filter.title().isEmpty()
? tr::lng_filters_all_short(tr::now)
: filter.title();
}) | ranges::to_vector;
slider->setSections(std::move(sections));
slider->fitWidthToSections(); slider->fitWidthToSections();
{ {
const auto reorderAll = session->user()->isPremium(); const auto reorderAll = session->user()->isPremium();

View file

@ -0,0 +1,72 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "ui/widgets/peer_bubble.h"
#include "data/data_peer.h"
#include "info/profile/info_profile_values.h"
#include "ui/controls/userpic_button.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/widgets/labels.h"
#include "styles/style_boxes.h"
#include "styles/style_channel_earn.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
namespace Ui {
object_ptr<Ui::RpWidget> CreatePeerBubble(
not_null<Ui::RpWidget*> parent,
not_null<PeerData*> peer) {
auto owned = object_ptr<Ui::RpWidget>(parent);
const auto peerBubble = owned.data();
peerBubble->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto left = Ui::CreateChild<Ui::UserpicButton>(
peerBubble,
peer,
st::uploadUserpicButton);
const auto right = Ui::CreateChild<Ui::FlatLabel>(
peerBubble,
Info::Profile::NameValue(peer),
st::channelEarnSemiboldLabel);
const auto padding = st::chatGiveawayPeerPadding
+ QMargins(st::chatGiveawayPeerPadding.left(), 0, 0, 0);
rpl::combine(
left->sizeValue(),
right->sizeValue()
) | rpl::start_with_next([=](
const QSize &leftSize,
const QSize &rightSize) {
peerBubble->resize(
leftSize.width() + rightSize.width() + rect::m::sum::h(padding),
leftSize.height());
left->moveToLeft(0, 0);
right->moveToRight(padding.right() + st::lineWidth, padding.top());
const auto maxRightSize = parent->width()
- rect::m::sum::h(st::boxRowPadding)
- rect::m::sum::h(padding)
- leftSize.width();
if ((rightSize.width() > maxRightSize) && (maxRightSize > 0)) {
right->resizeToWidth(maxRightSize);
}
}, peerBubble->lifetime());
peerBubble->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(peerBubble);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::windowBgOver);
const auto rect = peerBubble->rect();
const auto radius = rect.height() / 2;
p.drawRoundedRect(rect, radius, radius);
}, peerBubble->lifetime());
return owned;
}
} // namespace Ui

View file

@ -0,0 +1,26 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
template <typename Object>
class object_ptr;
class PeerData;
namespace Ui {
class RpWidget;
class VerticalLayout;
} // namespace Ui
namespace Ui {
[[nodiscard]] object_ptr<Ui::RpWidget> CreatePeerBubble(
not_null<Ui::RpWidget*> parent,
not_null<PeerData*> peer);
} // namespace Ui

View file

@ -281,7 +281,7 @@ private:
void addInfo(); void addInfo();
void addStoryArchive(); void addStoryArchive();
void addNewWindow(); void addNewWindow();
void addToggleFolder(); void addToggleFolder(bool onlyForChannels);
void addToggleUnreadMark(); void addToggleUnreadMark();
void addToggleArchive(); void addToggleArchive();
void addClearHistory(); void addClearHistory();
@ -620,12 +620,16 @@ void Filler::addStoryArchive() {
}, &st::menuIconStoriesArchiveSection); }, &st::menuIconStoriesArchiveSection);
} }
void Filler::addToggleFolder() { void Filler::addToggleFolder(bool onlyForChannels) {
const auto controller = _controller; const auto controller = _controller;
const auto history = _request.key.history(); const auto history = _request.key.history();
if (_topic || !history || !history->owner().chatsFilters().has()) { if (_topic || !history || !history->owner().chatsFilters().has()) {
return; return;
} }
if (onlyForChannels
&& (!history->peer->isChannel() || !history->inChatList())) {
return;
}
_addAction(PeerMenuCallback::Args{ _addAction(PeerMenuCallback::Args{
.text = tr::lng_filters_menu_add(tr::now), .text = tr::lng_filters_menu_add(tr::now),
.handler = nullptr, .handler = nullptr,
@ -1409,7 +1413,7 @@ void Filler::fillContextMenuActions() {
addToggleMuteSubmenu(false); addToggleMuteSubmenu(false);
addToggleUnreadMark(); addToggleUnreadMark();
addToggleTopicClosed(); addToggleTopicClosed();
addToggleFolder(); addToggleFolder(false);
if (const auto user = _peer->asUser()) { if (const auto user = _peer->asUser()) {
if (!user->isContact()) { if (!user->isContact()) {
addBlockUser(); addBlockUser();
@ -1459,6 +1463,7 @@ void Filler::fillProfileActions() {
addToggleTopicClosed(); addToggleTopicClosed();
addViewDiscussion(); addViewDiscussion();
addExportChat(); addExportChat();
addToggleFolder(true);
addBlockUser(); addBlockUser();
addReport(); addReport();
addLeaveChat(); addLeaveChat();
@ -2070,7 +2075,17 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
class ListBox final : public PeerListBox { class ListBox final : public PeerListBox {
public: public:
using PeerListBox::PeerListBox; ListBox(
QWidget *parent,
std::unique_ptr<PeerListController> controller,
Fn<void(not_null<ListBox*>)> init)
: PeerListBox(
parent,
std::move(controller),
[=](not_null<PeerListBox*> box) {
init(static_cast<ListBox*>(box.get()));
}) {
}
void setBottomSkip(int bottomSkip) { void setBottomSkip(int bottomSkip) {
PeerListBox::setInnerBottomSkip(bottomSkip); PeerListBox::setInnerBottomSkip(bottomSkip);
@ -2095,9 +2110,21 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
_forwardOptions = forwardOptions; _forwardOptions = forwardOptions;
} }
not_null<PeerListContent*> peerListContent() const {
return PeerListBox::content();
}
void setFilterId(FilterId filterId) {
_filterId = filterId;
}
[[nodiscard]] FilterId filterId() const {
return _filterId;
}
private: private:
rpl::event_stream<> _focusRequests; rpl::event_stream<> _focusRequests;
Ui::ForwardOptions _forwardOptions; Ui::ForwardOptions _forwardOptions;
FilterId _filterId = 0;
}; };
@ -2115,6 +2142,12 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
}) { }) {
} }
std::unique_ptr<PeerListRow> createRestoredRow(
not_null<PeerData*> peer) override final {
return ChooseRecipientBoxController::createRow(
peer->owner().history(peer));
}
using PeerListController::setSearchNoResultsText; using PeerListController::setSearchNoResultsText;
void rowClicked(not_null<PeerListRow*> row) override final { void rowClicked(not_null<PeerListRow*> row) override final {
@ -2167,166 +2200,48 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
base::unique_qptr<Ui::PopupMenu> menu; base::unique_qptr<Ui::PopupMenu> menu;
}; };
const auto applyFilter = [=](not_null<PeerListBox*> box, FilterId id) { const auto applyFilter = [=](not_null<ListBox*> box, FilterId id) {
box->scrollToY(0); box->scrollToY(0);
auto &filters = session->data().chatsFilters(); auto &filters = session->data().chatsFilters();
const auto &list = filters.list(); const auto &list = filters.list();
if (list.size() <= 1) { if (list.size() <= 1) {
return; return;
} }
const auto pinnedList = [&]( if (box->filterId() == id) {
not_null<Dialogs::MainList*> list,
bool foundSelf) {
const auto pinned = list->pinned()->order();
auto peers = std::vector<not_null<PeerData*>>();
peers.reserve(pinned.size());
for (const auto &pin : pinned) {
if (!foundSelf && pin.peer()->isSelf()) {
peers.insert(peers.begin(), pin.peer());
foundSelf = true;
} else {
peers.push_back(pin.peer());
}
}
if (!foundSelf) {
peers.insert(peers.begin(), session->user());
}
return peers;
};
const auto folder = session->data().folderLoaded(
Data::Folder::kId);
const auto pinned = pinnedList(
id
? filters.chatsList(id)
: session->data().chatsList(nullptr),
!!id);
const auto pinnedInFolder = (!id && folder)
? pinnedList(folder->chatsList(), true)
: std::vector<not_null<PeerData*>>();
box->peerListSortRows([&](
const PeerListRow &r1,
const PeerListRow &r2) {
{ // Pinned to top.
auto it1 = pinned.end();
auto it2 = pinned.end();
for (auto it = pinned.begin(); it != pinned.end(); ++it) {
if ((*it) == r1.peer()) {
it1 = it;
}
if ((*it) == r2.peer()) {
it2 = it;
}
if (it1 != pinned.end() && it2 != pinned.end()) {
break;
}
}
if (it1 == pinned.end() && it2 != pinned.end()) {
return false;
} else if (it2 == pinned.end() && it1 != pinned.end()) {
return true;
} else if (it1 != pinned.end() && it2 != pinned.end()) {
return it1 < it2;
}
}
{ // Pinned to bottom.
const auto &indexed = session->data().contactsNoChatsList();
auto it1 = indexed->end();
auto it2 = indexed->end();
for (auto it = indexed->begin(); it != indexed->end(); ++it) {
if (it->get()->key().peer() == r1.peer()) {
it1 = it;
}
if (it->get()->key().peer() == r2.peer()) {
it2 = it;
}
if (it1 != indexed->end() && it2 != indexed->end()) {
break;
}
}
if (it1 == indexed->end() && it2 != indexed->end()) {
return true;
} else if (it2 == indexed->end() && it1 != indexed->end()) {
return false;
} else if (it1 != indexed->end() && it2 != indexed->end()) {
return it1 > it2;
}
}
if (folder) {
const auto pinned1 = ranges::find(pinnedInFolder, r1.peer());
const auto pinned2 = ranges::find(pinnedInFolder, r2.peer());
const auto isPinned1 = pinned1 != pinnedInFolder.end();
const auto isPinned2 = pinned2 != pinnedInFolder.end();
if (isPinned1 && isPinned2) {
return pinned1 < pinned2;
}
const auto &indexed = folder->chatsList()->indexed();
auto it1 = indexed->end();
auto it2 = indexed->end();
for (auto it = indexed->begin(); it != indexed->end(); ++it) {
if (it->get()->key().peer() == r1.peer()) {
it1 = it;
}
if (it->get()->key().peer() == r2.peer()) {
it2 = it;
}
if (it1 != indexed->end() && it2 != indexed->end()) {
break;
}
}
const auto isFoldered1 = it1 != indexed->end();
const auto isFoldered2 = it2 != indexed->end();
if (isPinned1 && !isPinned2) {
return isFoldered2;
}
if (isPinned2 && !isPinned1) {
return !isFoldered1;
}
if (!isPinned1 && !isPinned2) {
if (!isFoldered1 && isFoldered2) {
return true;
} else if (!isFoldered2 && isFoldered1) {
return false;
} else if (isFoldered1 && isFoldered2) {
return it1 < it2;
}
}
}
const auto history1 = session->data().history(r1.peer());
const auto history2 = session->data().history(r2.peer());
const auto date1 = history1->lastMessage()
? history1->lastMessage()->date()
: TimeId(0);
const auto date2 = history2->lastMessage()
? history2->lastMessage()->date()
: TimeId(0);
return date1 > date2;
});
const auto filter = ranges::find(
list,
id,
&Data::ChatFilter::id);
if (filter == list.end()) {
return; return;
} }
box->peerListPartitionRows([&](const PeerListRow &row) { box->setFilterId(id);
const auto rowPtr = const_cast<PeerListRow*>(&row);
if (!filter->id()) { using SavedState = PeerListController::SavedStateBase;
box->peerListSetRowHidden(rowPtr, false); auto state = std::make_unique<PeerListState>();
} else { state->controllerState = std::make_unique<SavedState>();
const auto result = filter->contains(
session->data().history(row.peer())); const auto addList = [&](auto chats) {
box->peerListSetRowHidden(rowPtr, !result); for (const auto &row : chats->all()) {
if (const auto history = row->history()) {
state->list.push_back(history->peer);
}
} }
return false; };
});
box->peerListRefreshRows(); if (!id) {
state->list.push_back(session->user());
addList(session->data().chatsList()->indexed());
const auto folderId = Data::Folder::kId;
if (const auto folder = session->data().folderLoaded(folderId)) {
addList(folder->chatsList()->indexed());
}
addList(session->data().contactsNoChatsList());
} else {
addList(session->data().chatsFilters().chatsList(id)->indexed());
}
box->peerListContent()->restoreState(std::move(state));
}; };
const auto state = [&] { const auto state = [&] {
auto controller = std::make_unique<Controller>(session); auto controller = std::make_unique<Controller>(session);
const auto controllerRaw = controller.get(); const auto controllerRaw = controller.get();
auto init = [=](not_null<PeerListBox*> box) { auto init = [=](not_null<ListBox*> box) {
controllerRaw->setSearchNoResultsText( controllerRaw->setSearchNoResultsText(
tr::lng_bot_chats_not_found(tr::now)); tr::lng_bot_chats_not_found(tr::now));
box->setSpecialTabMode(true); box->setSpecialTabMode(true);

View file

@ -1874,6 +1874,10 @@ bool SessionController::switchInlineQuery(
int(textWithTags.text.size()), int(textWithTags.text.size()),
Ui::kQFixedMax Ui::kQFixedMax
}; };
if (to.currentReplyTo.messageId.msg == to.currentReplyTo.topicRootId
&& to.currentReplyTo.quote.empty()) {
to.currentReplyTo.messageId.msg = MsgId();
}
auto draft = std::make_unique<Data::Draft>( auto draft = std::make_unique<Data::Draft>(
textWithTags, textWithTags,
to.currentReplyTo, to.currentReplyTo,

View file

@ -1,7 +1,7 @@
AppVersion 5008002 AppVersion 5008003
AppVersionStrMajor 5.8 AppVersionStrMajor 5.8
AppVersionStrSmall 5.8.2 AppVersionStrSmall 5.8.3
AppVersionStr 5.8.2 AppVersionStr 5.8.3
BetaChannel 0 BetaChannel 0
AlphaVersion 0 AlphaVersion 0
AppVersionOriginal 5.8.2 AppVersionOriginal 5.8.3

View file

@ -21,6 +21,7 @@ set(style_files
ui/chat/chat.style ui/chat/chat.style
ui/effects/credits.style ui/effects/credits.style
ui/effects/premium.style ui/effects/premium.style
ui/color_indices.style
boxes/boxes.style boxes/boxes.style
dialogs/dialogs.style dialogs/dialogs.style
chat_helpers/chat_helpers.style chat_helpers/chat_helpers.style

View file

@ -1,3 +1,10 @@
5.8.3 (23.11.24)
- Ctrl+Click on Reply in groups to reply in another chat.
- Fix freeze on forward messages box opening.
- Improve phrases in bot subscriptions.
- Several bugfixes.
5.8.2 (19.11.24) 5.8.2 (19.11.24)
- Improve bottom label color in mini apps. - Improve bottom label color in mini apps.