diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index 82f1089f8..55143efd2 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -374,6 +374,8 @@ PRIVATE
boxes/peer_list_box.h
boxes/peer_list_controllers.cpp
boxes/peer_list_controllers.h
+ boxes/peer_list_widgets.cpp
+ boxes/peer_list_widgets.h
boxes/peer_lists_box.cpp
boxes/peer_lists_box.h
boxes/passcode_box.cpp
@@ -1631,6 +1633,8 @@ PRIVATE
ui/widgets/label_with_custom_emoji.h
ui/widgets/chat_filters_tabs_strip.cpp
ui/widgets/chat_filters_tabs_strip.h
+ ui/widgets/peer_bubble.cpp
+ ui/widgets/peer_bubble.h
ui/countryinput.cpp
ui/countryinput.h
ui/dynamic_thumbnails.cpp
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 9be66d4ae..19c05d3eb 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -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_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_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_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.";
@@ -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_channel" = "Sorry, copying from this channel is disabled by admins.";
"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_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?";
@@ -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" = "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_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_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}.";
@@ -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_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_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_photos#one" = "{count} photo";
"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_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_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_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_by_bot_about" = "{bot} has canceled your subscription.";
"lng_credits_subscription_status_on" = "renews on {date}";
"lng_credits_subscription_status_off" = "expires on {date}";
"lng_credits_subscription_status_none" = "expired on {date}";
"lng_credits_subscription_status_off_right" = "canceled";
"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#other" = "{count} Stars Needed";
diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index 8d93b797d..1f5ac3f65 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
+ Version="5.8.3.0" />
Telegram Desktop
Telegram Messenger LLP
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index 4f0000cc7..38113d8cc 100644
--- a/Telegram/Resources/winrc/Telegram.rc
+++ b/Telegram/Resources/winrc/Telegram.rc
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 5,8,2,0
- PRODUCTVERSION 5,8,2,0
+ FILEVERSION 5,8,3,0
+ PRODUCTVERSION 5,8,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
- VALUE "FileVersion", "5.8.2.0"
+ VALUE "FileVersion", "5.8.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "5.8.2.0"
+ VALUE "ProductVersion", "5.8.3.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index 3cb7b8d82..7a45674d7 100644
--- a/Telegram/Resources/winrc/Updater.rc
+++ b/Telegram/Resources/winrc/Updater.rc
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 5,8,2,0
- PRODUCTVERSION 5,8,2,0
+ FILEVERSION 5,8,3,0
+ PRODUCTVERSION 5,8,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater"
- VALUE "FileVersion", "5.8.2.0"
+ VALUE "FileVersion", "5.8.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "5.8.2.0"
+ VALUE "ProductVersion", "5.8.3.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/api/api_chat_invite.cpp b/Telegram/SourceFiles/api/api_chat_invite.cpp
index 0a53c61dd..134e3a005 100644
--- a/Telegram/SourceFiles/api/api_chat_invite.cpp
+++ b/Telegram/SourceFiles/api/api_chat_invite.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_chat_invite.h"
#include "apiwrap.h"
+#include "api/api_credits.h"
#include "boxes/premium_limits_box.h"
#include "core/application.h"
#include "data/components/credits.h"
@@ -295,20 +296,39 @@ void ConfirmSubscriptionBox(
const auto buttonWidth = state->saveButton
? state->saveButton->width()
: 0;
+ const auto finish = [=] {
+ state->api = std::nullopt;
+ state->loading.force_assign(false);
+ if (const auto strong = weak.data()) {
+ strong->closeBox();
+ }
+ };
state->api->request(
MTPpayments_SendStarsForm(
MTP_long(formId),
MTP_inputInvoiceChatInviteSubscription(MTP_string(hash)))
).done([=](const MTPpayments_PaymentResult &result) {
- state->api = std::nullopt;
- state->loading.force_assign(false);
result.match([&](const MTPDpayments_paymentResult &data) {
session->api().applyUpdates(data.vupdates());
}, [](const MTPDpayments_paymentVerificationNeeded &data) {
});
- if (weak) {
- box->closeBox();
+ const auto refill = session->data().activeCreditsSubsRebuilder();
+ const auto strong = weak.data();
+ if (!strong) {
+ return;
}
+ if (!refill) {
+ return finish();
+ }
+ const auto api
+ = strong->lifetime().make_state(
+ session->user(),
+ true,
+ true);
+ api->requestSubscriptions({}, [=](Data::CreditsStatusSlice d) {
+ refill->fire(std::move(d));
+ finish();
+ });
}).fail([=](const MTP::Error &error) {
const auto id = error.type();
if (weak) {
diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp
index 80e995219..175112c40 100644
--- a/Telegram/SourceFiles/api/api_credits.cpp
+++ b/Telegram/SourceFiles/api/api_credits.cpp
@@ -133,17 +133,26 @@ constexpr auto kTransactionsLimit = 100;
}
[[nodiscard]] Data::SubscriptionEntry SubscriptionFromTL(
- const MTPStarsSubscription &tl) {
+ const MTPStarsSubscription &tl,
+ not_null peer) {
return Data::SubscriptionEntry{
.id = qs(tl.data().vid()),
.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),
.subscription = Data::PeerSubscription{
.credits = tl.data().vpricing().data().vamount().v,
.period = tl.data().vpricing().data().vperiod().v,
},
.barePeerId = peerFromMTP(tl.data().vpeer()).value,
+ .photoId = (tl.data().vphoto()
+ ? peer->owner().photoFromWeb(
+ *tl.data().vphoto(),
+ ImageLocation())->id
+ : 0),
.cancelled = tl.data().is_canceled(),
+ .cancelledByBot = tl.data().is_bot_canceled(),
.expired = (base::unixtime::now() > tl.data().vuntil_date().v),
.canRefulfill = tl.data().is_can_refulfill(),
};
@@ -166,7 +175,7 @@ constexpr auto kTransactionsLimit = 100;
if (const auto history = data.vsubscriptions()) {
subscriptions.reserve(history->v.size());
for (const auto &tl : history->v) {
- subscriptions.push_back(SubscriptionFromTL(tl));
+ subscriptions.push_back(SubscriptionFromTL(tl, peer));
}
}
return Data::CreditsStatusSlice{
@@ -463,4 +472,20 @@ Data::CreditsGiveawayOptions CreditsGiveawayOptions::options() const {
return _options;
}
+void EditCreditsSubscription(
+ not_null session,
+ const QString &id,
+ bool cancel,
+ Fn done,
+ Fn 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
diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h
index e178cb866..4f697cf09 100644
--- a/Telegram/SourceFiles/api/api_credits.h
+++ b/Telegram/SourceFiles/api/api_credits.h
@@ -109,4 +109,11 @@ private:
[[nodiscard]] rpl::producer> PremiumPeerBot(
not_null session);
+void EditCreditsSubscription(
+ not_null session,
+ const QString &id,
+ bool cancel,
+ Fn done,
+ Fn fail);
+
} // namespace Api
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 6ffccae96..22bf7cd0b 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -582,6 +582,14 @@ void ApiWrap::sendMessageFail(
: tr::lng_error_noforwards_group(tr::now), kJoinErrorDuration);
} else if (error == u"PREMIUM_ACCOUNT_REQUIRED"_q) {
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)) {
Assert(randomId != 0);
diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp
index ff0226a87..ce1418618 100644
--- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp
+++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp
@@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/filters/edit_filter_chats_list.h"
+#include "data/data_chat_filters.h"
#include "data/data_premium_limits.h"
+#include "data/data_session.h"
#include "history/history.h"
#include "window/window_session_controller.h"
#include "lang/lang_keys.h"
@@ -125,7 +127,15 @@ Flag TypeRow::flag() const {
}
ExceptionRow::ExceptionRow(not_null 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));
}
}
diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
index a3d385c72..542189659 100644
--- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp
+++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
@@ -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()) {
- constexpr auto kOneLineCount = 22;
- const auto oneLine = entry.id.size() <= 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');
- }
- }
- }
+ constexpr auto kOneLineCount = 24;
+ const auto oneLine = entry.id.length() <= kOneLineCount;
auto label = object_ptr(
table,
rpl::single(
- Ui::Text::Wrapped(
- { oneLine ? entry.id : std::move(multiLine) },
- EntityType::Code,
- {})),
+ Ui::Text::Wrapped({ entry.id }, EntityType::Code, {})),
oneLine
? st::giveawayGiftCodeValue
: st::giveawayGiftCodeValueMultiline);
@@ -1324,11 +1319,24 @@ void AddSubscriptionEntryTable(
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(s.barePeerId);
+ const auto user = peerIsUser(peerId)
+ ? controller->session().data().peer(peerId)->asUser()
+ : nullptr;
AddTableRow(
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,
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.subscription.period > 0) {
const auto subscribed = s.until.addSecs(-s.subscription.period);
diff --git a/Telegram/SourceFiles/boxes/peer_list_widgets.cpp b/Telegram/SourceFiles/boxes/peer_list_widgets.cpp
new file mode 100644
index 000000000..dfa729eb6
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/peer_list_widgets.cpp
@@ -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;
+
+} // namespace
+
+PeerListWidgets::PeerListWidgets(
+ not_null parent,
+ not_null controller)
+: Ui::RpWidget(parent)
+, _controller(controller)
+, _st(controller->computeListSt()) {
+ _content = base::make_unique_q(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 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 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::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 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 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 uiShow) {
+ _uiShow = std::move(uiShow);
+}
+
+void PeerListWidgetsDelegate::peerListSetHideEmpty(bool hide) {
+ Unexpected("...PeerListWidgetsDelegate::peerListSetHideEmpty");
+}
+
+void PeerListWidgetsDelegate::peerListAppendRow(
+ std::unique_ptr row) {
+ _content->appendRow(std::move(row));
+}
+
+void PeerListWidgetsDelegate::peerListAppendSearchRow(
+ std::unique_ptr row) {
+ Unexpected("...PeerListWidgetsDelegate::peerListAppendSearchRow");
+}
+
+void PeerListWidgetsDelegate::peerListAppendFoundRow(
+ not_null row) {
+ Unexpected("...PeerListWidgetsDelegate::peerListAppendFoundRow");
+}
+
+void PeerListWidgetsDelegate::peerListPrependRow(
+ std::unique_ptr row) {
+ Unexpected("...PeerListWidgetsDelegate::peerListPrependRow");
+}
+
+void PeerListWidgetsDelegate::peerListPrependRowFromSearchResult(
+ not_null row) {
+ Unexpected(
+ "...PeerListWidgetsDelegate::peerListPrependRowFromSearchResult");
+}
+
+PeerListRow* PeerListWidgetsDelegate::peerListFindRow(PeerListRowId id) {
+ return _content->findRow(id);
+}
+
+auto PeerListWidgetsDelegate::peerListLastRowMousePosition()
+-> std::optional {
+ Unexpected("...PeerListWidgetsDelegate::peerListLastRowMousePosition");
+}
+
+void PeerListWidgetsDelegate::peerListUpdateRow(not_null row) {
+ _content->updateRow(row);
+}
+
+void PeerListWidgetsDelegate::peerListRemoveRow(not_null row) {
+ Unexpected("...PeerListWidgetsDelegate::peerListRemoveRow");
+}
+
+void PeerListWidgetsDelegate::peerListConvertRowToSearchResult(
+ not_null row) {
+ Unexpected(
+ "...PeerListWidgetsDelegate::peerListConvertRowToSearchResult");
+}
+
+void PeerListWidgetsDelegate::peerListSetRowChecked(
+ not_null row,
+ bool checked) {
+ Unexpected("...PeerListWidgetsDelegate::peerListSetRowChecked");
+}
+
+void PeerListWidgetsDelegate::peerListSetRowHidden(
+ not_null row,
+ bool hidden) {
+ Unexpected("...PeerListWidgetsDelegate::peerListSetRowHidden");
+}
+
+void PeerListWidgetsDelegate::peerListSetForeignRowChecked(
+ not_null row,
+ bool checked,
+ anim::type animated) {
+}
+
+int PeerListWidgetsDelegate::peerListFullRowsCount() {
+ return _content->fullRowsCount();
+}
+
+not_null PeerListWidgetsDelegate::peerListRowAt(int index) {
+ return _content->rowAt(index);
+}
+
+int PeerListWidgetsDelegate::peerListSearchRowsCount() {
+ Unexpected("...PeerListWidgetsDelegate::peerListSearchRowsCount");
+}
+
+not_null PeerListWidgetsDelegate::peerListSearchRowAt(int) {
+ Unexpected("...PeerListWidgetsDelegate::peerListSearchRowAt");
+}
+
+void PeerListWidgetsDelegate::peerListRefreshRows() {
+ _content->refreshRows();
+}
+
+void PeerListWidgetsDelegate::peerListSetDescription(
+ object_ptr) {
+ Unexpected("...PeerListWidgetsDelegate::peerListSetDescription");
+}
+
+void PeerListWidgetsDelegate::peerListSetSearchNoResults(
+ object_ptr) {
+ Unexpected("...PeerListWidgetsDelegate::peerListSetSearchNoResults");
+}
+
+void PeerListWidgetsDelegate::peerListSetAboveWidget(
+ object_ptr) {
+ Unexpected("...PeerListWidgetsDelegate::peerListSetAboveWidget");
+}
+
+void PeerListWidgetsDelegate::peerListSetAboveSearchWidget(
+ object_ptr) {
+ Unexpected("...PeerListWidgetsDelegate::peerListSetAboveSearchWidget");
+}
+
+void PeerListWidgetsDelegate::peerListSetBelowWidget(
+ object_ptr) {
+ Unexpected("...PeerListWidgetsDelegate::peerListSetBelowWidget");
+}
+
+void PeerListWidgetsDelegate::peerListSetSearchMode(PeerListSearchMode mode) {
+ Unexpected("...PeerListWidgetsDelegate::peerListSetSearchMode");
+}
+
+void PeerListWidgetsDelegate::peerListMouseLeftGeometry() {
+ Unexpected("...PeerListWidgetsDelegate::peerListMouseLeftGeometry");
+}
+
+void PeerListWidgetsDelegate::peerListSortRows(
+ Fn) {
+ Unexpected("...PeerListWidgetsDelegate::peerListSortRows");
+}
+
+int PeerListWidgetsDelegate::peerListPartitionRows(
+ Fn border) {
+ Unexpected("...PeerListWidgetsDelegate::peerListPartitionRows");
+}
+
+State PeerListWidgetsDelegate::peerListSaveState() const {
+ Unexpected("...PeerListWidgetsDelegate::peerListSaveState");
+ return nullptr;
+}
+
+void PeerListWidgetsDelegate::peerListRestoreState(
+ std::unique_ptr state) {
+ Unexpected("...PeerListWidgetsDelegate::peerListRestoreState");
+}
+
+void PeerListWidgetsDelegate::peerListShowRowMenu(
+ not_null row,
+ bool highlightRow,
+ Fn)> 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 PeerListWidgetsDelegate::peerListUiShow() {
+ Expects(_uiShow != nullptr);
+ return _uiShow;
+}
+
+void PeerListWidgetsDelegate::peerListAddSelectedPeerInBunch(
+ not_null peer) {
+ Unexpected("...PeerListWidgetsDelegate::peerListAddSelectedPeerInBunch");
+}
+
+void PeerListWidgetsDelegate::peerListAddSelectedRowInBunch(
+ not_null row) {
+ Unexpected("...PeerListWidgetsDelegate::peerListAddSelectedRowInBunch");
+}
+
+void PeerListWidgetsDelegate::peerListFinishSelectedRowsBunch() {
+ Unexpected("...PeerListWidgetsDelegate::peerListFinishSelectedRowsBunch");
+}
+
+void PeerListWidgetsDelegate::peerListSetTitle(rpl::producer title) {
+ Unexpected("...PeerListWidgetsDelegate::peerListSetTitle");
+}
+
+void PeerListWidgetsDelegate::peerListSetAdditionalTitle(
+ rpl::producer title) {
+ Unexpected("...PeerListWidgetsDelegate::peerListSetAdditionalTitle");
+}
+
+bool PeerListWidgetsDelegate::peerListIsRowChecked(
+ not_null row) {
+ Unexpected("...PeerListWidgetsDelegate::peerListIsRowChecked");
+ return false;
+}
+
+void PeerListWidgetsDelegate::peerListScrollToTop() {
+ Unexpected("...PeerListWidgetsDelegate::peerListScrollToTop");
+}
+
+int PeerListWidgetsDelegate::peerListSelectedRowsCount() {
+ Unexpected("...PeerListWidgetsDelegate::peerListSelectedRowsCount");
+ return 0;
+}
diff --git a/Telegram/SourceFiles/boxes/peer_list_widgets.h b/Telegram/SourceFiles/boxes/peer_list_widgets.h
new file mode 100644
index 000000000..d139487bc
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/peer_list_widgets.h
@@ -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 parent,
+ not_null controller);
+
+ crl::time paintRow(
+ Painter &p,
+ crl::time now,
+ bool selected,
+ not_null row);
+ void appendRow(std::unique_ptr row);
+ PeerListRow* findRow(PeerListRowId id);
+ void updateRow(not_null row);
+ int fullRowsCount();
+ [[nodiscard]] not_null rowAt(int index);
+ void refreshRows();
+
+private:
+ const not_null _controller;
+ const style::PeerList &_st;
+ base::unique_qptr _content;
+
+ std::vector> _rows;
+ std::map> _rowsById;
+ std::map>> _rowsByPeer;
+};
+
+class PeerListWidgetsDelegate : public PeerListDelegate {
+public:
+ void setContent(PeerListWidgets *content);
+ void setUiShow(std::shared_ptr uiShow);
+
+ void peerListSetHideEmpty(bool hide) override;
+ void peerListAppendRow(std::unique_ptr row) override;
+ void peerListAppendSearchRow(std::unique_ptr row) override;
+ void peerListAppendFoundRow(not_null row) override;
+ void peerListPrependRow(std::unique_ptr row) override;
+ void peerListPrependRowFromSearchResult(
+ not_null row) override;
+ PeerListRow *peerListFindRow(PeerListRowId id) override;
+ std::optional peerListLastRowMousePosition() override;
+ void peerListUpdateRow(not_null row) override;
+ void peerListRemoveRow(not_null row) override;
+ void peerListConvertRowToSearchResult(
+ not_null row) override;
+ void peerListSetRowChecked(
+ not_null row,
+ bool checked) override;
+ void peerListSetRowHidden(
+ not_null row,
+ bool hidden) override;
+ void peerListSetForeignRowChecked(
+ not_null row,
+ bool checked,
+ anim::type animated) override;
+ int peerListFullRowsCount() override;
+ not_null peerListRowAt(int index) override;
+ int peerListSearchRowsCount() override;
+ not_null peerListSearchRowAt(int index) override;
+ void peerListRefreshRows() override;
+ void peerListSetDescription(object_ptr) override;
+ void peerListSetSearchNoResults(object_ptr) override;
+ void peerListSetAboveWidget(object_ptr) override;
+ void peerListSetAboveSearchWidget(object_ptr) override;
+ void peerListSetBelowWidget(object_ptr) override;
+ void peerListSetSearchMode(PeerListSearchMode mode) override;
+ void peerListMouseLeftGeometry() override;
+ void peerListSortRows(
+ Fn) override;
+ int peerListPartitionRows(Fn border) override;
+ std::unique_ptr peerListSaveState() const override;
+ void peerListRestoreState(std::unique_ptr state) override;
+ void peerListShowRowMenu(
+ not_null row,
+ bool highlightRow,
+ Fn)> destroyed = nullptr) override;
+ void peerListSelectSkip(int direction) override;
+ void peerListPressLeftToContextMenu(bool shown) override;
+ bool peerListTrackRowPressFromGlobal(QPoint globalPosition) override;
+ std::shared_ptr peerListUiShow() override;
+ void peerListAddSelectedPeerInBunch(not_null peer) override;
+ void peerListAddSelectedRowInBunch(not_null row) override;
+ void peerListFinishSelectedRowsBunch() override;
+ void peerListSetTitle(rpl::producer title) override;
+ void peerListSetAdditionalTitle(rpl::producer title) override;
+ bool peerListIsRowChecked(not_null row) override;
+ void peerListScrollToTop() override;
+ int peerListSelectedRowsCount() override;
+
+private:
+ PeerListWidgets *_content = nullptr;
+ std::shared_ptr _uiShow;
+
+};
diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp
index 3ac13da20..e42151d75 100644
--- a/Telegram/SourceFiles/boxes/send_credits_box.cpp
+++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp
@@ -26,17 +26,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_credits_graphics.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h"
+#include "ui/effects/credits_graphics.h"
#include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg.
#include "ui/image/image_prepare.h"
#include "ui/layers/generic_box.h"
+#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/text/text_utilities.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
+#include "ui/widgets/peer_bubble.h"
#include "styles/style_boxes.h"
+#include "styles/style_chat.h"
#include "styles/style_credits.h"
#include "styles/style_giveaway.h"
+#include "styles/style_info.h" // inviteLinkSubscribeBoxTerms
#include "styles/style_layers.h"
#include "styles/style_premium.h"
#include "styles/style_settings.h"
@@ -92,6 +97,44 @@ struct PaidMediaData {
};
}
+void AddTerms(
+ not_null box,
+ not_null button,
+ const style::Box &stBox) {
+ const auto terms = Ui::CreateChild(
+ 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{
+ .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 SendCreditsConfirmText(
not_null session,
not_null form) {
@@ -150,6 +193,18 @@ struct PaidMediaData {
}
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(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
@@ -190,6 +245,57 @@ struct PaidMediaData {
st::defaultUserpicButton);
}
+[[nodiscard]] not_null SendCreditsBadge(
+ not_null parent,
+ int credits) {
+ const auto widget = Ui::CreateChild(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(
+ 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
void SendCreditsBox(
@@ -203,7 +309,8 @@ void SendCreditsBox(
rpl::variable confirmButtonBusy = false;
};
const auto state = box->lifetime().make_state();
- box->setStyle(st::giveawayGiftCodeBox);
+ const auto &stBox = st::giveawayGiftCodeBox;
+ box->setStyle(stBox);
box->setNoContentMargin(true);
const auto session = form->invoice.session;
@@ -245,14 +352,36 @@ void SendCreditsBox(
content,
SendCreditsThumbnail(content, session, form.get(), photoSize)));
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);
box->addRow(object_ptr>(
box,
object_ptr(
box,
- tr::lng_credits_box_out_title(),
+ form->invoice.subscriptionPeriod
+ ? rpl::single(form->title)
+ : tr::lng_credits_box_out_title(),
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>(
+ box,
+ Ui::CreatePeerBubble(box, bot)));
+ Ui::AddSkip(content);
+ }
Ui::AddSkip(content);
box->addRow(object_ptr>(
box,
@@ -306,6 +435,9 @@ void SendCreditsBox(
}
}).send();
});
+ if (form->invoice.subscriptionPeriod) {
+ AddTerms(box, button, stBox);
+ }
{
using namespace Info::Statistics;
const auto loadingAnimation = InfiniteRadialAnimationWidget(
@@ -317,12 +449,14 @@ void SendCreditsBox(
SetButtonMarkedLabel(
button,
rpl::combine(
- tr::lng_credits_box_out_confirm(
- lt_count,
- rpl::single(form->invoice.amount) | tr::to_count(),
- lt_emoji,
- rpl::single(CreditsEmojiSmall(session)),
- Ui::Text::RichLangValue),
+ (form->invoice.subscriptionPeriod
+ ? tr::lng_credits_box_out_subscription_confirm
+ : tr::lng_credits_box_out_confirm)(
+ lt_count,
+ rpl::single(form->invoice.amount) | tr::to_count(),
+ lt_emoji,
+ rpl::single(CreditsEmojiSmall(session)),
+ Ui::Text::RichLangValue),
state->confirmButtonBusy.value()
) | rpl::map([](TextWithEntities &&text, bool busy) {
return busy ? TextWithEntities() : std::move(text);
@@ -332,7 +466,7 @@ void SendCreditsBox(
box->getDelegate()->style().button.textFg->c);
const auto buttonWidth = st::boxWidth
- - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding);
+ - rect::m::sum::h(stBox.buttonPadding);
button->widthValue() | rpl::filter([=] {
return (button->widthNoMargins() != buttonWidth);
}) | rpl::start_with_next([=] {
@@ -341,15 +475,11 @@ void SendCreditsBox(
{
const auto close = Ui::CreateChild(
- box.get(),
+ content,
st::boxTitleClose);
- close->setClickedCallback([=] {
- box->closeBox();
- });
- box->widthValue(
- ) | rpl::start_with_next([=](int width) {
+ close->setClickedCallback([=] { box->closeBox(); });
+ content->widthValue() | rpl::start_with_next([=](int) {
close->moveToRight(0, 0);
- close->raise();
}, close->lifetime());
}
diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp
index 118532d16..7d5793727 100644
--- a/Telegram/SourceFiles/calls/calls_instance.cpp
+++ b/Telegram/SourceFiles/calls/calls_instance.cpp
@@ -669,7 +669,8 @@ bool Instance::inCall() const {
return false;
}
const auto state = _currentCall->state();
- return (state != Call::State::Busy);
+ return (state != Call::State::Busy)
+ && (state != Call::State::WaitingUserConfirmation);
}
bool Instance::inGroupCall() const {
diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp
index 6f0ab0f6a..74ddcb8f2 100644
--- a/Telegram/SourceFiles/calls/calls_panel.cpp
+++ b/Telegram/SourceFiles/calls/calls_panel.cpp
@@ -993,7 +993,12 @@ void Panel::paint(QRect clip) {
bool Panel::handleClose() const {
if (_call) {
- window()->hide();
+ if (_call->state() == Call::State::WaitingUserConfirmation
+ || _call->state() == Call::State::Busy) {
+ _call->hangup();
+ } else {
+ window()->hide();
+ }
return true;
}
return false;
@@ -1028,6 +1033,7 @@ void Panel::stateChanged(State state) {
_startVideo = base::make_unique_q(
widget(),
st::callStartVideo);
+ _startVideo->show();
_startVideo->setText(tr::lng_call_start_video());
_startVideo->clicks() | rpl::map_to(true) | rpl::start_to_stream(
_startOutgoingRequests,
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index eb693524e..2ddd76836 100644
--- a/Telegram/SourceFiles/core/version.h
+++ b/Telegram/SourceFiles/core/version.h
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs;
-constexpr auto AppVersion = 5008002;
-constexpr auto AppVersionStr = "5.8.2";
+constexpr auto AppVersion = 5008003;
+constexpr auto AppVersionStr = "5.8.3";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index 95ced4eb8..cd1ea9c65 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -1980,6 +1980,19 @@ rpl::producer<> Session::pinnedDialogsOrderUpdated() const {
return _pinnedDialogsOrderUpdated.events();
}
+Session::CreditsSubsRebuilderPtr Session::createCreditsSubsRebuilder() {
+ if (auto result = activeCreditsSubsRebuilder()) {
+ return result;
+ }
+ auto result = std::make_shared();
+ _creditsSubsRebuilder = result;
+ return result;
+}
+
+Session::CreditsSubsRebuilderPtr Session::activeCreditsSubsRebuilder() const {
+ return _creditsSubsRebuilder.lock();
+}
+
void Session::registerHeavyViewPart(not_null view) {
_heavyViewParts.emplace(view);
}
diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h
index b9d048062..84e735c09 100644
--- a/Telegram/SourceFiles/data/data_session.h
+++ b/Telegram/SourceFiles/data/data_session.h
@@ -70,6 +70,7 @@ class Chatbots;
class BusinessInfo;
struct ReactionId;
struct UnavailableReason;
+struct CreditsStatusSlice;
struct RepliesReadTillUpdate {
FullMsgId id;
@@ -337,6 +338,11 @@ public:
void notifyPinnedDialogsOrderUpdated();
[[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const;
+ using CreditsSubsRebuilder = rpl::event_stream;
+ using CreditsSubsRebuilderPtr = std::shared_ptr;
+ [[nodiscard]] CreditsSubsRebuilderPtr createCreditsSubsRebuilder();
+ [[nodiscard]] CreditsSubsRebuilderPtr activeCreditsSubsRebuilder() const;
+
void registerRestricted(
not_null item,
const QString &reason);
@@ -1095,6 +1101,8 @@ private:
MessageIdsList _mimeForwardIds;
+ std::weak_ptr _creditsSubsRebuilder;
+
using CredentialsWithGeneration = std::pair<
const Passport::SavedCredentials,
int>;
diff --git a/Telegram/SourceFiles/data/data_subscriptions.h b/Telegram/SourceFiles/data/data_subscriptions.h
index 69af0d4d6..c0d4de5a2 100644
--- a/Telegram/SourceFiles/data/data_subscriptions.h
+++ b/Telegram/SourceFiles/data/data_subscriptions.h
@@ -18,6 +18,7 @@ struct PeerSubscription final {
}
};
+using PhotoId = uint64;
struct SubscriptionEntry final {
explicit operator bool() const {
return !id.isEmpty();
@@ -25,10 +26,14 @@ struct SubscriptionEntry final {
QString id;
QString inviteHash;
+ QString title;
+ QString slug;
QDateTime until;
PeerSubscription subscription;
uint64 barePeerId = 0;
+ PhotoId photoId = PhotoId(0);
bool cancelled = false;
+ bool cancelledByBot = false;
bool expired = false;
bool canRefulfill = false;
};
diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h
index 56e84f48c..3a19eec68 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_entry.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h
@@ -295,7 +295,7 @@ auto Entry::unreadStateChangeNotifier(bool required) {
_flags |= Flag::InUnreadChangeBlock;
const auto notify = required && inChatList();
const auto wasState = notify ? chatListUnreadState() : UnreadState();
- return gsl::finally([=] {
+ return gsl::finally([=, this] {
_flags &= ~Flag::InUnreadChangeBlock;
if (notify) {
Assert(inChatList());
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index 5a1d7e731..edb19fe09 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -130,19 +130,6 @@ int BinarySearchBlocksOrItems(const T &list, int edge) {
return start;
}
-[[nodiscard]] bool CanSendReply(not_null 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
// 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
? selected.item
: still)->fullId();
- if (canSendReply) {
- _widget->replyToMessage({
- .messageId = replyToItemId,
- .quote = selected.text,
- .quoteOffset = selected.offset,
- });
- if (!selected.text.empty()) {
- _widget->clearSelected();
- }
- } else {
- HistoryView::Controls::ShowReplyToChatBox(show, {
- .messageId = replyToItemId,
- .quote = selected.text,
- .quoteOffset = selected.offset,
- });
+ _widget->replyToMessage({
+ .messageId = replyToItemId,
+ .quote = selected.text,
+ .quoteOffset = selected.offset,
+ });
+ if (!selected.text.empty()) {
+ _widget->clearSelected();
}
};
return false;
@@ -2644,26 +2623,13 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
const auto quoteOffset = selected.offset;
text.replace('&', u"&&"_q);
_menu->addAction(text, [=] {
- const auto still = session->data().message(itemId);
- const auto forceAnotherChat = base::IsCtrlPressed()
- && still
- && still->allowsForward();
- if (canSendReply && !forceAnotherChat) {
- _widget->replyToMessage({
- .messageId = itemId,
- .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,
- });
+ _widget->replyToMessage({
+ .messageId = itemId,
+ .quote = quote,
+ .quoteOffset = quoteOffset,
+ });
+ if (!quote.empty()) {
+ _widget->clearSelected();
}
}, &st::menuIconReply);
}
@@ -4847,3 +4813,16 @@ auto HistoryInner::DelegateMixin()
-> std::unique_ptr {
return std::make_unique();
}
+
+bool CanSendReply(not_null 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()));
+}
diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h
index 818b5435b..4f0ba7bfc 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.h
+++ b/Telegram/SourceFiles/history/history_inner_widget.h
@@ -555,3 +555,5 @@ private:
bool _wasForceClickPreview = false;
};
+
+[[nodiscard]] bool CanSendReply(not_null item);
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index 833e66e8b..332f2cc08 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -4701,6 +4701,21 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
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 result = PreparedServiceText();
if (out()) {
@@ -5504,7 +5519,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
prepareSecureValuesSent,
prepareContactSignUp,
prepareProximityReached,
- PrepareErrorText,
+ preparePaymentSentMe,
PrepareErrorText,
prepareGroupCall,
prepareInviteToGroupCall,
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index 8bcfc7570..4031c19f3 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -2028,24 +2028,12 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(
UserData *samePeerBot,
MsgId samePeerReplyTo) {
if (samePeerBot) {
- if (_history) {
- const auto textWithTags = TextWithTags{
- '@' + samePeerBot->username() + ' ' + query,
- TextWithTags::Tags(),
- };
- MessageCursor cursor = {
- int(textWithTags.text.size()),
- int(textWithTags.text.size()),
- Ui::kQFixedMax,
- };
- _history->setLocalDraft(std::make_unique(
- textWithTags,
- FullReplyTo(),
- cursor,
- Data::WebPageDraft()));
- applyDraft();
- return true;
+ const auto to = controller()->currentDialogsEntryState();
+ if (!to.key.owningHistory()) {
+ return false;
}
+ controller()->switchInlineQuery(to, samePeerBot, query);
+ return true;
} else if (const auto bot = _peer ? _peer->asUser() : nullptr) {
const auto to = bot->isBot()
? bot->botInfo->inlineReturnTo
@@ -2273,7 +2261,7 @@ void HistoryWidget::showHistory(
_showAtMsgHighlightPart = {};
_showAtMsgHighlightPartOffsetHint = 0;
- const auto wasDialogsEntryState = computeDialogsEntryState();
+ const auto wasState = controller()->currentDialogsEntryState();
const auto startBot = (showAtMsgId == ShowAndStartBotMsgId);
if (startBot) {
showAtMsgId = ShowAtTheEndMsgId;
@@ -2378,8 +2366,8 @@ void HistoryWidget::showHistory(
if (const auto user = _peer->asUser()) {
if (const auto &info = user->botInfo) {
if (startBot) {
- if (wasDialogsEntryState.key) {
- info->inlineReturnTo = wasDialogsEntryState;
+ if (wasState.key) {
+ info->inlineReturnTo = wasState;
}
sendBotStartCommand();
_history->clearLocalDraft({});
@@ -2614,8 +2602,8 @@ void HistoryWidget::showHistory(
if (const auto user = _peer->asUser()) {
if (const auto &info = user->botInfo) {
if (startBot) {
- if (wasDialogsEntryState.key) {
- info->inlineReturnTo = wasDialogsEntryState;
+ if (wasState.key) {
+ info->inlineReturnTo = wasState;
}
sendBotStartCommand();
}
@@ -8060,7 +8048,15 @@ void HistoryWidget::clearFieldText(
void HistoryWidget::replyToMessage(FullReplyTo id) {
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));
+ }
}
}
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
index 95c48c0e2..92e02a3c7 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
@@ -1887,7 +1887,7 @@ not_null ListWidget::elementPathShiftGradient() {
}
void ListWidget::elementReplyTo(const FullReplyTo &to) {
- replyToMessageRequestNotify(to);
+ replyToMessageRequestNotify(to, base::IsCtrlPressed());
}
void ListWidget::elementStartInteraction(not_null view) {
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index 1e18a5cd8..4b065e664 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -1659,7 +1659,8 @@ void Message::draw(Painter &p, const PaintContext &context) const {
(right
- (st::msgSelectionOffset * progress - st.size) / 2
- st::msgPadding.right() / 2
- - st.size),
+ - st.size
+ - st::historyScroll.deltax),
rect::bottom(g) - st.size - st::msgSelectionBottomSkip);
{
p.setPen(QPen(st.border, st.width));
diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp
index e75a360c7..41de275e9 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp
@@ -255,7 +255,6 @@ DocumentData *Sticker::document() {
void Sticker::stickerClearLoopPlayed() {
_oncePlayed = false;
- _premiumEffectPlayed = false;
_premiumEffectSkipped = false;
}
diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp
index 515ab33e1..a9bc9687b 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp
@@ -634,10 +634,13 @@ QSize WebPage::countOptimalSize() {
_durationWidth = st::msgDateFont->width(_duration);
}
if (!_openButton.isEmpty()) {
- accumulate_max(
- maxWidth,
- rect::m::sum::h(st::historyPageButtonPadding)
- + _openButton.maxWidth());
+ const auto w = rect::m::sum::h(st::historyPageButtonPadding)
+ + _openButton.maxWidth();
+ if (sponsored) {
+ accumulate_max(maxWidth, w);
+ } else {
+ maxWidth += w;
+ }
}
maxWidth += rect::m::sum::h(padding);
minHeight += rect::m::sum::v(padding);
diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp
index b78da5c17..1fbf97797 100644
--- a/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp
+++ b/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp
@@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/labels.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/ui_utility.h"
+#include "styles/style_color_indices.h"
#include "styles/style_credits.h"
#include "styles/style_giveaway.h"
#include "styles/style_info.h"
@@ -59,7 +60,6 @@ namespace {
constexpr auto kDoneTooltipDuration = 5 * crl::time(1000);
constexpr auto kAdditionalPrizeLengthMax = 128;
-constexpr auto kColorIndexCredits = int(1);
[[nodiscard]] QDateTime ThreeDaysAfterToday() {
auto dateNow = QDateTime::currentDateTime();
@@ -370,7 +370,7 @@ void CreateGiveawayBox(
prepaid->credits
? GiveawayType::PrepaidCredits
: GiveawayType::Prepaid,
- prepaid->credits ? kColorIndexCredits : prepaid->id,
+ prepaid->credits ? st::colorIndexOrange : prepaid->id,
tr::lng_boosts_prepaid_giveaway_single(),
prepaid->credits
? tr::lng_boosts_prepaid_giveaway_credits_status(
@@ -508,7 +508,7 @@ void CreateGiveawayBox(
object_ptr(
box,
GiveawayType::Credits,
- kColorIndexCredits,
+ st::colorIndexOrange,
tr::lng_credits_summary_title(),
tr::lng_giveaway_create_subtitle(),
std::move(badge)));
diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway_type_row.cpp b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway_type_row.cpp
index b97184dba..60c1814ab 100644
--- a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway_type_row.cpp
+++ b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway_type_row.cpp
@@ -16,14 +16,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/checkbox.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
+#include "styles/style_color_indices.h"
#include "styles/style_giveaway.h"
#include "styles/style_statistics.h"
namespace Giveaway {
-constexpr auto kColorIndexSpecific = int(4);
-constexpr auto kColorIndexRandom = int(2);
-
GiveawayTypeRow::GiveawayTypeRow(
not_null parent,
Type type,
@@ -32,7 +30,7 @@ GiveawayTypeRow::GiveawayTypeRow(
: GiveawayTypeRow(
parent,
type,
- (type == Type::SpecificUsers) ? kColorIndexSpecific : kColorIndexRandom,
+ (type == Type::SpecificUsers) ? st::colorIndexBlue : st::colorIndexGreen,
(type == Type::SpecificUsers)
? tr::lng_giveaway_award_option()
: (type == Type::Random)
diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/boosts/info_boosts_inner_widget.cpp
index e2436585c..5b6648220 100644
--- a/Telegram/SourceFiles/info/channel_statistics/boosts/info_boosts_inner_widget.cpp
+++ b/Telegram/SourceFiles/info/channel_statistics/boosts/info_boosts_inner_widget.cpp
@@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/slider_natural_width.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/ui_utility.h"
+#include "styles/style_color_indices.h"
#include "styles/style_dialogs.h" // dialogsSearchTabs
#include "styles/style_giveaway.h"
#include "styles/style_info.h"
@@ -339,7 +340,6 @@ void InnerWidget::fill() {
Ui::AddSkip(inner);
if (!status.prepaidGiveaway.empty()) {
- constexpr auto kColorIndexCredits = int(1);
const auto multiplier = Api::PremiumGiftCodeOptions(_peer)
.giveawayBoostsPerPremium();
Ui::AddSkip(inner);
@@ -352,7 +352,7 @@ void InnerWidget::fill() {
g.credits
? GiveawayTypeRow::Type::PrepaidCredits
: GiveawayTypeRow::Type::Prepaid,
- g.credits ? kColorIndexCredits : g.id,
+ g.credits ? st::colorIndexOrange : g.id,
g.credits
? tr::lng_boosts_prepaid_giveaway_single()
: tr::lng_boosts_prepaid_giveaway_quantity(
diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp
index 3f98b5f23..3a2250f39 100644
--- a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp
+++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp
@@ -52,6 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/vertical_list.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/label_with_custom_emoji.h"
+#include "ui/widgets/peer_bubble.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/slider_natural_width.h"
#include "ui/wrap/slide_wrap.h"
@@ -1199,58 +1200,10 @@ void InnerWidget::fill() {
AddRecipient(box, recipient);
}
if (isIn) {
- const auto peerBubble = box->addRow(
+ box->addRow(
object_ptr>(
box,
- object_ptr(box)))->entity();
- peerBubble->setAttribute(
- Qt::WA_TransparentForMouseEvents);
- const auto left = Ui::CreateChild(
- peerBubble,
- peer,
- st::uploadUserpicButton);
- const auto right = Ui::CreateChild(
- 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());
+ Ui::CreatePeerBubble(box, peer)));
}
const auto closeBox = [=] { box->closeBox(); };
{
diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp
index abbd690c7..3dbe08b96 100644
--- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp
+++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_credits.h"
#include "api/api_statistics.h"
#include "boxes/peer_list_controllers.h"
+#include "boxes/peer_list_widgets.h"
#include "chat_helpers/stickers_gift_box_pack.h"
#include "core/ui_integration.h" // Core::MarkedTextContext.
#include "data/data_channel.h"
@@ -24,6 +25,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "main/session/session_show.h"
#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/outline_segments.h" // Ui::UnreadStoryOutlineGradient.
#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/vertical_layout.h"
#include "styles/style_boxes.h"
+#include "styles/style_color_indices.h"
#include "styles/style_credits.h"
#include "styles/style_dialogs.h" // dialogsStoriesFull.
#include "styles/style_layers.h" // boxRowPadding.
@@ -47,9 +51,6 @@ namespace Info::Statistics {
namespace {
using BoostCallback = Fn;
-constexpr auto kColorIndexCredits = int(1);
-constexpr auto kColorIndexUnclaimed = int(3);
-constexpr auto kColorIndexPending = int(4);
[[nodiscard]] PeerListRowId UniqueRowIdFromEntry(
const Data::CreditsHistoryEntry &entry) {
@@ -70,6 +71,17 @@ void AddSubtitle(
{ 0, -subtitlePadding.top(), 0, -subtitlePadding.bottom() });
}
+[[nodiscard]] object_ptr CreateShowMoreButton(
+ not_null parent,
+ rpl::producer title) {
+ auto owned = object_ptr(
+ parent,
+ std::move(title),
+ st::statisticsShowMoreButton);
+ Ui::AddToggleUpDownArrowToMoreButton(owned.data());
+ return owned;
+}
+
[[nodiscard]] QString FormatText(
int value1, tr::phrase phrase1,
int value2, tr::phrase phrase2,
@@ -475,10 +487,10 @@ BoostRow::BoostRow(const Data::Boost &boost)
, _boost(boost)
, _userpic(
Ui::EmptyUserpic::UserpicColor(boost.credits
- ? kColorIndexCredits
+ ? st::colorIndexOrange
: boost.isUnclaimed
- ? kColorIndexUnclaimed
- : kColorIndexPending),
+ ? st::colorIndexSea
+ : st::colorIndexBlue),
QString()) {
init();
}
@@ -763,6 +775,18 @@ public:
bool selected,
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:
void init();
@@ -776,8 +800,12 @@ private:
QString _title;
QString _name;
+ Ui::Text::String _description;
Ui::Text::String _rightText;
+ std::shared_ptr _descriptionThumbnail;
+ QImage _descriptionThumbnailCache;
+
base::has_weak_ptr _guard;
};
@@ -822,33 +850,37 @@ void CreditsRow::init() {
const auto name = !isSpecial
? PeerListRow::generateName()
: Ui::GenerateEntryName(_entry).text;
- _name = (_entry.reaction || _entry.stargift || _entry.bareGiveawayMsgId)
- ? Ui::GenerateEntryName(_entry).text
- : _entry.title.isEmpty()
+ _name = _entry.title.isEmpty()
+ ? name
+ : (!_entry.subscriptionUntil.isNull() && !isSpecial)
? name
: _entry.title;
- const auto joiner = QString(QChar(' ')) + QChar(8212) + QChar(' ');
setSkipPeerBadge(true);
- PeerListRow::setCustomStatus(
- langDateTime(_entry.date)
- + (_entry.floodSkip
- ? (joiner + tr::lng_credits_box_history_entry_floodskip_about(
- tr::now,
- lt_count_decimal,
- _entry.floodSkip))
- : _entry.refunded
- ? (joiner + tr::lng_channel_earn_history_return(tr::now))
- : _entry.pending
- ? (joiner + tr::lng_channel_earn_history_pending(tr::now))
- : _entry.failed
- ? (joiner + tr::lng_channel_earn_history_failed(tr::now))
- : !_entry.subscriptionUntil.isNull()
- ? (joiner
- + tr::lng_credits_box_history_entry_subscription(tr::now))
- : QString())
- + ((_entry.gift && isSpecial)
- ? (joiner + tr::lng_credits_box_history_entry_anonymous(tr::now))
- : ((_name == name) ? QString() : (joiner + name))));
+ const auto description = _entry.floodSkip
+ ? tr::lng_credits_box_history_entry_floodskip_about(
+ tr::now,
+ lt_count_decimal,
+ _entry.floodSkip)
+ : (!_entry.subscriptionUntil.isNull() && !_entry.title.isEmpty())
+ ? _entry.title
+ : _entry.refunded
+ ? tr::lng_channel_earn_history_return(tr::now)
+ : _entry.pending
+ ? tr::lng_channel_earn_history_pending(tr::now)
+ : _entry.failed
+ ? tr::lng_channel_earn_history_failed(tr::now)
+ : !_entry.subscriptionUntil.isNull()
+ ? tr::lng_credits_box_history_entry_subscription(tr::now)
+ : (_entry.peerType
+ == Data::CreditsHistoryEntry::PeerType::PremiumBot)
+ ? tr::lng_credits_box_history_entry_via_premium_bot(tr::now)
+ : (_entry.gift && isSpecial)
+ ? 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) {
PeerListRow::setCustomStatus((_subscription.expired
? tr::lng_credits_subscription_status_none
@@ -858,6 +890,24 @@ void CreditsRow::init() {
tr::now,
lt_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();
if (_entry) {
@@ -894,23 +944,40 @@ const Data::SubscriptionEntry &CreditsRow::subscription() const {
}
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) {
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 {
if (_rightLabel) {
return _rightLabel->size;
- } else if (_subscription.cancelled || _subscription.expired) {
- const auto text = _subscription.cancelled
- ? tr::lng_credits_subscription_status_off_right(tr::now)
- : tr::lng_credits_subscription_status_none_right(tr::now);
- return QSize(
- st::contactsStatusFont->width(text) + st::boxRowPadding.right(),
- _rowHeight);
+ } else if (const auto t = RightActionText(_subscription); !t.isEmpty()) {
+ const auto lines = t.split('\n');
+ auto maxWidth = 0;
+ for (const auto &line : lines) {
+ const auto width = st::contactsStatusFont->width(line);
+ if (width > maxWidth) {
+ maxWidth = width;
+ }
+ }
+ return QSize(maxWidth + st::boxRowPadding.right(), _rowHeight);
} else if (_subscription || _entry) {
return QSize(
_rightText.maxWidth() + st::boxRowPadding.right() / 2,
@@ -940,18 +1007,31 @@ void CreditsRow::rightActionPaint(
const auto rightSkip = st::boxRowPadding.right();
if (_rightLabel) {
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;
y += _rowHeight / 2;
p.setFont(statusFont);
p.setPen(st::attentionButtonFg);
- p.drawTextRight(
- rightSkip,
- y - statusFont->height / 2,
- outerWidth,
- _subscription.expired
- ? tr::lng_credits_subscription_status_none_right(tr::now)
- : tr::lng_credits_subscription_status_off_right(tr::now));
+
+ const auto lines = t.split('\n');
+ if (lines.size() > 1) {
+ const auto rect = QRect(x, 0, outerWidth - x, _rowHeight);
+ const auto lineHeight = statusFont->height;
+ const auto totalHeight = lines.size() * lineHeight;
+ 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;
}
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 {
public:
explicit CreditsController(CreditsDescriptor d);
@@ -1010,7 +1127,7 @@ CreditsController::CreditsController(CreditsDescriptor d)
.session = _session,
.customEmojiRepaint = [] {},
}) {
- PeerListController::setStyleOverrides(&st::boostsListBox);
+ PeerListController::setStyleOverrides(&st::creditsHistoryEntriesList);
}
Main::Session &CreditsController::session() const {
@@ -1052,7 +1169,9 @@ void CreditsController::applySlice(const Data::CreditsStatusSlice &slice) {
.entry = i,
.subscription = s,
.context = _context,
- .rowHeight = computeListSt().item.height,
+ .rowHeight = ((!s || !s.title.isEmpty())
+ ? computeListSt().item
+ : st::boostsListBox.item).height,
.updateCallback = [=](not_null row) {
delegate()->peerListUpdateRow(row);
},
@@ -1239,28 +1358,36 @@ void AddCreditsHistoryList(
not_null bot,
bool in,
bool out,
- bool subscription) {
+ bool subs) {
struct State final {
- State(
- CreditsDescriptor d,
- std::shared_ptr show)
- : delegate(std::move(show))
- , controller(std::move(d)) {
+ State(CreditsDescriptor d) : controller(std::move(d)) {
}
- PeerListContentDelegateShow delegate;
+ std::optional creditsDelegate;
+ std::optional subscriptionDelegate;
CreditsController controller;
};
const auto state = container->lifetime().make_state(
- CreditsDescriptor{ firstSlice, callback, bot, in, out, subscription },
- show);
+ CreditsDescriptor{ firstSlice, callback, bot, in, out, subs });
+ if (subs) {
+ state->subscriptionDelegate.emplace();
+ state->subscriptionDelegate->setUiShow(show);
+ state->subscriptionDelegate->setContent(container->add(
+ object_ptr(container, &state->controller)));
+ state->controller.setDelegate(&(*state->subscriptionDelegate));
+ } else {
+ state->creditsDelegate.emplace(show);
+ state->creditsDelegate->setContent(container->add(
+ object_ptr(container, &state->controller)));
+ state->controller.setDelegate(&(*state->creditsDelegate));
+ }
- state->delegate.setContent(container->add(
- object_ptr(container, &state->controller)));
- state->controller.setDelegate(&state->delegate);
-
- const auto wrap = AddShowMoreButton(
- container,
- tr::lng_stories_show_more());
+ const auto wrap = container->add(
+ object_ptr>(
+ container,
+ CreateShowMoreButton(container, tr::lng_stories_show_more())),
+ subs
+ ? QMargins()
+ : QMargins(0, -st::settingsButton.padding.top(), 0, 0));
const auto showMore = [=] {
if (!state->controller.skipRequest()) {
@@ -1277,16 +1404,11 @@ void AddCreditsHistoryList(
not_null*> AddShowMoreButton(
not_null container,
rpl::producer title) {
- const auto wrap = container->add(
+ return container->add(
object_ptr>(
container,
- object_ptr(
- container,
- std::move(title),
- st::statisticsShowMoreButton)),
+ CreateShowMoreButton(container, std::move(title))),
{ 0, -st::settingsButton.padding.top(), 0, 0 });
- Ui::AddToggleUpDownArrowToMoreButton(wrap->entity());
- return wrap;
}
} // namespace Info::Statistics
diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp
index dadb510b3..0d6c091b3 100644
--- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp
+++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp
@@ -643,12 +643,13 @@ void ConfirmEmojiStatusBox(
done(true);
});
box->addButton(tr::lng_cancel(), [=] {
- const auto was = *set;
box->closeBox();
- if (!was) {
+ });
+ box->boxClosing() | rpl::start_with_next([=] {
+ if (!*set) {
done(false);
}
- });
+ }, box->lifetime());
}
class BotAction final : public Ui::Menu::ItemBase {
@@ -954,7 +955,12 @@ void WebViewInstance::resolve() {
requestSimple();
});
}, [&](WebViewSourceLinkApp data) {
- resolveApp(data.appname, data.token, !_context.maySkipConfirmation);
+ resolveApp(
+ data.appname,
+ data.token,
+ (_context.maySkipConfirmation
+ ? ConfirmType::None
+ : ConfirmType::Always));
}, [&](WebViewSourceLinkBotProfile) {
confirmOpen([=] {
requestMain();
@@ -1002,14 +1008,14 @@ bool WebViewInstance::openAppFromBotMenuLink() {
if (appname.isEmpty()) {
return false;
}
- resolveApp(appname, params.value(u"startapp"_q), true);
+ resolveApp(appname, params.value(u"startapp"_q), ConfirmType::Once);
return true;
}
void WebViewInstance::resolveApp(
const QString &appname,
const QString &startparam,
- bool forceConfirmation) {
+ ConfirmType confirmType) {
const auto already = _session->data().findBotApp(_bot->id, appname);
_requestId = _session->api().request(MTPmessages_GetBotApp(
MTP_inputBotAppShortName(
@@ -1029,8 +1035,11 @@ void WebViewInstance::resolveApp(
close();
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 forceConfirmation = data.is_inactive()
+ || (confirmType == ConfirmType::Always);
// Check if this app can be added to main menu.
// On fail it'll still be opened.
@@ -1043,7 +1052,7 @@ void WebViewInstance::resolveApp(
} else if (confirm) {
confirmAppOpen(writeAccess, [=](bool allowWrite) {
requestApp(allowWrite);
- });
+ }, forceConfirmation);
} else {
requestApp(false);
}
@@ -1090,10 +1099,18 @@ void WebViewInstance::confirmOpen(Fn done) {
void WebViewInstance::confirmAppOpen(
bool writeAccess,
- Fn done) {
+ Fn done,
+ bool forceConfirmation) {
+ if (!forceConfirmation
+ && (_bot->isVerified()
+ || _session->local().isBotTrustedOpenWebView(_bot->id))) {
+ done(writeAccess);
+ return;
+ }
_parentShow->show(Box([=](not_null box) {
const auto allowed = std::make_shared();
const auto callback = [=](Fn close) {
+ _session->local().markBotTrustedOpenWebView(_bot->id);
done((*allowed) && (*allowed)->checked());
close();
};
diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h
index febf74dfa..f0cb146a7 100644
--- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h
+++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h
@@ -230,12 +230,20 @@ private:
void requestWithMenuAdd();
void maybeChooseAndRequestButton(PeerTypes supported);
+ enum class ConfirmType : uchar {
+ Always,
+ Once,
+ None,
+ };
void resolveApp(
const QString &appname,
const QString &startparam,
- bool forceConfirmation);
+ ConfirmType confirmType);
void confirmOpen(Fn done);
- void confirmAppOpen(bool writeAccess, Fn done);
+ void confirmAppOpen(
+ bool writeAccess,
+ Fn done,
+ bool forceConfirmation);
struct ShowArgs {
QString url;
diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp
index 7941404c4..382226840 100644
--- a/Telegram/SourceFiles/payments/payments_form.cpp
+++ b/Telegram/SourceFiles/payments/payments_form.cpp
@@ -456,6 +456,8 @@ void Form::requestForm() {
const auto amount = tlPrices.empty()
? 0
: tlPrices.front().data().vamount().v;
+ const auto subscriptionPeriod
+ = data.vinvoice().data().vsubscription_period().value_or(0);
if (currency != ::Ui::kCreditsCurrency || !amount) {
using Type = Error::Type;
_updates.fire(Error{ Type::Form, u"Bad Stars Form."_q });
@@ -467,6 +469,7 @@ void Form::requestForm() {
.credits = amount,
.currency = currency,
.amount = amount,
+ .subscriptionPeriod = subscriptionPeriod,
};
const auto formData = CreditsFormData{
.id = _id,
diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h
index 9b0d38d4d..d93fce51f 100644
--- a/Telegram/SourceFiles/payments/payments_form.h
+++ b/Telegram/SourceFiles/payments/payments_form.h
@@ -170,6 +170,7 @@ struct InvoiceCredits {
uint64 amount = 0;
bool extended = false;
PeerId giftPeerId = PeerId(0);
+ int subscriptionPeriod = 0;
};
struct InvoiceStarGift {
diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp
index 6f348df4f..0b9e292fd 100644
--- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp
+++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp
@@ -94,14 +94,14 @@ void ProcessCreditsPayment(
Ui::SendCreditsBox,
form,
[=] {
- *unsuccessful = false;
- if (const auto widget = fireworks.data()) {
- Ui::StartFireworks(widget);
- }
- if (const auto onstack = maybeReturnToBot) {
- onstack(CheckoutResult::Paid);
- }
- }));
+ *unsuccessful = false;
+ if (const auto widget = fireworks.data()) {
+ Ui::StartFireworks(widget);
+ }
+ if (const auto onstack = maybeReturnToBot) {
+ onstack(CheckoutResult::Paid);
+ }
+ }));
box->boxClosing() | rpl::start_with_next([=] {
crl::on_main([=] {
if (*unsuccessful) {
diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp
index b4cd7396d..f22b94a7c 100644
--- a/Telegram/SourceFiles/settings/settings_credits.cpp
+++ b/Telegram/SourceFiles/settings/settings_credits.cpp
@@ -191,6 +191,19 @@ void Credits::setupSubscriptions(not_null container) {
fill(std::move(d));
});
}
+ {
+ using Rebuilder = Data::Session::CreditsSubsRebuilder;
+ using RebuilderPtr = std::shared_ptr;
+ const auto rebuilder = content->lifetime().make_state(
+ 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 container) {
diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp
index fc1b1d8e8..4a190dc3a 100644
--- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp
+++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp
@@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "payments/payments_checkout_process.h"
#include "payments/payments_form.h"
+#include "payments/payments_non_panel_process.h"
#include "settings/settings_common_session.h"
#include "settings/settings_credits.h"
#include "statistics/widgets/chart_header_widget.h"
@@ -346,6 +347,37 @@ void AddViewMediaHandler(
}, thumb->lifetime());
}
+void AddMiniStars(
+ not_null content,
+ not_null widget,
+ const style::UserpicButton &stUser,
+ int boxWidth,
+ float64 heightRatio) {
+ using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
+ const auto stars = widget->lifetime().make_state(
+ 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
SubscriptionRightLabel PaintSubscriptionRightLabelCallback(
@@ -664,31 +696,12 @@ void BoostCreditsBox(
{
const auto &stUser = st::premiumGiftsUserpicButton;
const auto widget = content->add(object_ptr(content));
- using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
- const auto stars = widget->lifetime().make_state(
- widget,
- false,
- Ui::Premium::MiniStars::Type::BiStars);
- stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
- widget->resize(
- st::boxWidth - stUser.photoSize,
- stUser.photoSize * 1.3);
+ AddMiniStars(content, widget, stUser, st::boxWidth, 1.3);
const auto svg = std::make_shared(
Ui::Premium::ColorizedSvg(
Ui::Premium::CreditsIconGradientStops()));
- content->sizeValue(
- ) | rpl::start_with_next([=](const QSize &size) {
- widget->moveToLeft(stUser.photoSize / 2, 0);
- const auto starsRect = Rect(widget->size());
- stars->setPosition(starsRect.topLeft());
- stars->setSize(starsRect.size());
- widget->lower();
- }, widget->lifetime());
- widget->paintRequest(
- ) | rpl::start_with_next([=](const QRect &r) {
+ widget->paintRequest() | rpl::start_with_next([=](const QRect &r) {
auto p = QPainter(widget);
- p.fillRect(r, Qt::transparent);
- stars->paint(p);
svg->render(
&p,
QRectF(
@@ -790,6 +803,28 @@ void BoostCreditsBox(
}, button->lifetime());
}
+void ProcessReceivedSubscriptions(
+ QPointer weak,
+ not_null 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(
+ session->user(),
+ true,
+ true);
+ api->requestSubscriptions({}, [=](Data::CreditsStatusSlice first) {
+ rebuilder->fire(std::move(first));
+ if (const auto strong = weak.data()) {
+ strong->closeBox();
+ }
+ });
+ }
+}
+
void ReceiptCreditsBox(
not_null box,
not_null controller,
@@ -820,6 +855,7 @@ void ReceiptCreditsBox(
const auto nonConvertible = (gotStarGift && !e.starsConverted);
box->setStyle(st::giveawayGiftCodeBox);
+ box->setWidth(st::boxWideWidth);
box->setNoContentMargin(true);
const auto content = box->verticalLayout();
@@ -851,6 +887,20 @@ void ReceiptCreditsBox(
content,
GenericEntryPhoto(content, callback, stUser.photoSize)));
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(content);
+ AddMiniStars(content, widget, stUser, st::boxWideWidth, 1.5);
+ }
+ const auto photoId = s.photoId ? s.photoId : e.photoId;
+ const auto callback = [=](Fn update) {
+ return Ui::GenerateCreditsPaintEntryCallback(
+ session->data().photo(photoId),
+ std::move(update));
+ };
+ content->add(object_ptr>(
+ content,
+ GenericEntryPhoto(content, callback, stUser.photoSize)));
} else if (peer && !e.gift) {
if (e.subscriptionUntil.isNull() && s.until.isNull()) {
content->add(object_ptr>(
@@ -950,11 +1000,13 @@ void ReceiptCreditsBox(
box,
object_ptr(
box,
- rpl::single(!s.until.isNull()
+ rpl::single(!s.title.isEmpty()
+ ? s.title
+ : !s.until.isNull()
? tr::lng_credits_box_subscription_title(tr::now)
: isPrize
? tr::lng_credits_box_history_entry_giveaway_name(tr::now)
- : !e.subscriptionUntil.isNull()
+ : (!e.subscriptionUntil.isNull() && e.title.isEmpty())
? tr::lng_credits_box_history_entry_subscription(tr::now)
: !e.title.isEmpty()
? e.title
@@ -1270,16 +1322,48 @@ void ReceiptCreditsBox(
st::creditsBoxAboutDivider)));
}
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);
auto label = object_ptr(
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_on_about(
lt_date,
rpl::single(langDayOfMonthFull(s.until.date()))),
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);
}
box->addRow(
@@ -1290,41 +1374,26 @@ void ReceiptCreditsBox(
if (e.peerType == Data::CreditsHistoryEntry::PeerType::PremiumBot) {
const auto widget = Ui::CreateChild(content);
- using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
- const auto stars = widget->lifetime().make_state(
- 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());
+ AddMiniStars(content, widget, stUser, st::boxWideWidth, 2);
}
+ 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)
- && !s.inviteHash.isEmpty();
- const auto toCancel = !toRenew && s;
+ && (rejoinByApi || rejoinByInvite)
+ && !s.cancelledByBot;
+ const auto toRejoin = (s.cancelled || s.expired)
+ && rejoinBySlug
+ && !s.cancelledByBot;
auto confirmText = rpl::conditional(
state->confirmButtonBusy.value(),
rpl::single(QString()),
(toRenew
? tr::lng_credits_subscription_off_button()
- : toCancel
- ? tr::lng_credits_subscription_on_button()
+ : toRejoin
+ ? tr::lng_credits_subscription_off_rejoin_button()
: (canConvert || couldConvert || nonConvertible)
? (e.savedToProfile
? tr::lng_gift_display_on_page_hide()
@@ -1372,45 +1441,57 @@ void ReceiptCreditsBox(
save,
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) {
Api::CheckChatInvite(controller, s.inviteHash, nullptr, [=] {
- if (const auto strong = weak.data()) {
- strong->closeBox();
- }
+ ProcessReceivedSubscriptions(weak, session);
});
} else {
- using Flag = MTPpayments_ChangeStarsSubscription::Flag;
- session->api().request(
- MTPpayments_ChangeStarsSubscription(
- MTP_flags(Flag::f_canceled),
- 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) {
+ const auto done = [=] {
+ ProcessReceivedSubscriptions(weak, session);
+ };
+ const auto fail = [=, show = box->uiShow()](const QString &e) {
if (const auto strong = weak.data()) {
state->confirmButtonBusy = false;
}
- show->showToast(error.type());
- }).send();
+ show->showToast(e);
+ };
+ Api::EditCreditsSubscription(session, s.id, false, done, fail);
}
};
+ const auto willBusy = toRejoin
+ || (peer
+ && (toRenew || canConvert || couldConvert || nonConvertible));
+ if (willBusy) {
+ const auto close = Ui::CreateChild(
+ 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), [=] {
if (state->confirmButtonBusy.current()
|| state->convertButtonBusy.current()) {
return;
}
- state->confirmButtonBusy = true;
- if (peer
- && (toRenew
- || toCancel
- || canConvert
- || couldConvert
- || nonConvertible)) {
+ if (willBusy) {
+ state->confirmButtonBusy = true;
send();
} else {
box->closeBox();
@@ -1424,7 +1505,7 @@ void ReceiptCreditsBox(
AddChildToWidgetCenter(button, loadingAnimation);
loadingAnimation->showOn(state->confirmButtonBusy.value());
}
- const auto buttonWidth = st::boxWidth
+ const auto buttonWidth = st::boxWideWidth
- rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding);
button->widthValue() | rpl::filter([=] {
diff --git a/Telegram/SourceFiles/ui/color_indices.style b/Telegram/SourceFiles/ui/color_indices.style
new file mode 100644
index 000000000..3acd347ab
--- /dev/null
+++ b/Telegram/SourceFiles/ui/color_indices.style
@@ -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;
diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style
index 734b2a2f0..84008e849 100644
--- a/Telegram/SourceFiles/ui/effects/credits.style
+++ b/Telegram/SourceFiles/ui/effects/credits.style
@@ -156,3 +156,20 @@ giftListAbout: FlatLabel(defaultFlatLabel) {
giftListAboutMargin: margins(12px, 24px, 12px, 24px);
giftBoxEmojiToggleTop: 7px;
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);
diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp
index 76d899c76..d79741299 100644
--- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp
+++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp
@@ -533,7 +533,7 @@ Fn)> PaintPreviewCallback(
extended,
std::move(update));
};
- } else if (entry.photoId) {
+ } else if (entry.photoId && entry.subscriptionUntil.isNull()) {
const auto photo = session->data().photo(entry.photoId);
return [=](Fn update) {
return GenerateCreditsPaintEntryCallback(
diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp
index ac6f6dcbd..b2286be5f 100644
--- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp
+++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp
@@ -57,6 +57,26 @@ ChatsFiltersTabs::ChatsFiltersTabs(
Ui::DiscreteSlider::setSelectOnPress(false);
}
+bool ChatsFiltersTabs::setSectionsAndCheckChanged(
+ std::vector &§ions) {
+ 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 {
const auto widths = countSectionsWidths(0);
auto result = 0;
diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h
index c66818fbd..9bd4cf67e 100644
--- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h
+++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h
@@ -27,6 +27,8 @@ public:
not_null parent,
const style::SettingsSlider &st);
+ bool setSectionsAndCheckChanged(std::vector &§ions);
+
[[nodiscard]] int centerOfSection(int section) const;
void fitWidthToSections();
void setUnreadCount(int index, int unreadCount, bool muted);
diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp
index fe8710f30..2c4472e79 100644
--- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp
+++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp
@@ -293,15 +293,18 @@ not_null AddChatFiltersTabsStrip(
if ((list.size() <= 1 && !slider->width()) || state->ignoreRefresh) {
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();
- 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();
{
const auto reorderAll = session->user()->isPremium();
diff --git a/Telegram/SourceFiles/ui/widgets/peer_bubble.cpp b/Telegram/SourceFiles/ui/widgets/peer_bubble.cpp
new file mode 100644
index 000000000..87ac897d3
--- /dev/null
+++ b/Telegram/SourceFiles/ui/widgets/peer_bubble.cpp
@@ -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 CreatePeerBubble(
+ not_null parent,
+ not_null peer) {
+ auto owned = object_ptr(parent);
+ const auto peerBubble = owned.data();
+ peerBubble->setAttribute(Qt::WA_TransparentForMouseEvents);
+ const auto left = Ui::CreateChild(
+ peerBubble,
+ peer,
+ st::uploadUserpicButton);
+ const auto right = Ui::CreateChild(
+ 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
diff --git a/Telegram/SourceFiles/ui/widgets/peer_bubble.h b/Telegram/SourceFiles/ui/widgets/peer_bubble.h
new file mode 100644
index 000000000..9c0b657d9
--- /dev/null
+++ b/Telegram/SourceFiles/ui/widgets/peer_bubble.h
@@ -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
+class object_ptr;
+
+class PeerData;
+
+namespace Ui {
+class RpWidget;
+class VerticalLayout;
+} // namespace Ui
+
+namespace Ui {
+
+[[nodiscard]] object_ptr CreatePeerBubble(
+ not_null parent,
+ not_null peer);
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp
index 54c15bbe6..4ef4df316 100644
--- a/Telegram/SourceFiles/window/window_peer_menu.cpp
+++ b/Telegram/SourceFiles/window/window_peer_menu.cpp
@@ -281,7 +281,7 @@ private:
void addInfo();
void addStoryArchive();
void addNewWindow();
- void addToggleFolder();
+ void addToggleFolder(bool onlyForChannels);
void addToggleUnreadMark();
void addToggleArchive();
void addClearHistory();
@@ -620,12 +620,16 @@ void Filler::addStoryArchive() {
}, &st::menuIconStoriesArchiveSection);
}
-void Filler::addToggleFolder() {
+void Filler::addToggleFolder(bool onlyForChannels) {
const auto controller = _controller;
const auto history = _request.key.history();
if (_topic || !history || !history->owner().chatsFilters().has()) {
return;
}
+ if (onlyForChannels
+ && (!history->peer->isChannel() || !history->inChatList())) {
+ return;
+ }
_addAction(PeerMenuCallback::Args{
.text = tr::lng_filters_menu_add(tr::now),
.handler = nullptr,
@@ -1409,7 +1413,7 @@ void Filler::fillContextMenuActions() {
addToggleMuteSubmenu(false);
addToggleUnreadMark();
addToggleTopicClosed();
- addToggleFolder();
+ addToggleFolder(false);
if (const auto user = _peer->asUser()) {
if (!user->isContact()) {
addBlockUser();
@@ -1459,6 +1463,7 @@ void Filler::fillProfileActions() {
addToggleTopicClosed();
addViewDiscussion();
addExportChat();
+ addToggleFolder(true);
addBlockUser();
addReport();
addLeaveChat();
@@ -2070,7 +2075,17 @@ QPointer ShowForwardMessagesBox(
class ListBox final : public PeerListBox {
public:
- using PeerListBox::PeerListBox;
+ ListBox(
+ QWidget *parent,
+ std::unique_ptr controller,
+ Fn)> init)
+ : PeerListBox(
+ parent,
+ std::move(controller),
+ [=](not_null box) {
+ init(static_cast(box.get()));
+ }) {
+ }
void setBottomSkip(int bottomSkip) {
PeerListBox::setInnerBottomSkip(bottomSkip);
@@ -2095,9 +2110,21 @@ QPointer ShowForwardMessagesBox(
_forwardOptions = forwardOptions;
}
+ not_null peerListContent() const {
+ return PeerListBox::content();
+ }
+
+ void setFilterId(FilterId filterId) {
+ _filterId = filterId;
+ }
+ [[nodiscard]] FilterId filterId() const {
+ return _filterId;
+ }
+
private:
rpl::event_stream<> _focusRequests;
Ui::ForwardOptions _forwardOptions;
+ FilterId _filterId = 0;
};
@@ -2115,6 +2142,12 @@ QPointer ShowForwardMessagesBox(
}) {
}
+ std::unique_ptr createRestoredRow(
+ not_null peer) override final {
+ return ChooseRecipientBoxController::createRow(
+ peer->owner().history(peer));
+ }
+
using PeerListController::setSearchNoResultsText;
void rowClicked(not_null row) override final {
@@ -2167,166 +2200,48 @@ QPointer ShowForwardMessagesBox(
base::unique_qptr menu;
};
- const auto applyFilter = [=](not_null box, FilterId id) {
+ const auto applyFilter = [=](not_null box, FilterId id) {
box->scrollToY(0);
auto &filters = session->data().chatsFilters();
const auto &list = filters.list();
if (list.size() <= 1) {
return;
}
- const auto pinnedList = [&](
- not_null list,
- bool foundSelf) {
- const auto pinned = list->pinned()->order();
- auto peers = std::vector>();
- 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>();
- 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()) {
+ if (box->filterId() == id) {
return;
}
- box->peerListPartitionRows([&](const PeerListRow &row) {
- const auto rowPtr = const_cast(&row);
- if (!filter->id()) {
- box->peerListSetRowHidden(rowPtr, false);
- } else {
- const auto result = filter->contains(
- session->data().history(row.peer()));
- box->peerListSetRowHidden(rowPtr, !result);
+ box->setFilterId(id);
+
+ using SavedState = PeerListController::SavedStateBase;
+ auto state = std::make_unique();
+ state->controllerState = std::make_unique();
+
+ const auto addList = [&](auto chats) {
+ 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 = [&] {
auto controller = std::make_unique(session);
const auto controllerRaw = controller.get();
- auto init = [=](not_null box) {
+ auto init = [=](not_null box) {
controllerRaw->setSearchNoResultsText(
tr::lng_bot_chats_not_found(tr::now));
box->setSpecialTabMode(true);
diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp
index 95b46dff1..593608cfb 100644
--- a/Telegram/SourceFiles/window/window_session_controller.cpp
+++ b/Telegram/SourceFiles/window/window_session_controller.cpp
@@ -1874,6 +1874,10 @@ bool SessionController::switchInlineQuery(
int(textWithTags.text.size()),
Ui::kQFixedMax
};
+ if (to.currentReplyTo.messageId.msg == to.currentReplyTo.topicRootId
+ && to.currentReplyTo.quote.empty()) {
+ to.currentReplyTo.messageId.msg = MsgId();
+ }
auto draft = std::make_unique(
textWithTags,
to.currentReplyTo,
diff --git a/Telegram/build/version b/Telegram/build/version
index ff4691843..66fe3884c 100644
--- a/Telegram/build/version
+++ b/Telegram/build/version
@@ -1,7 +1,7 @@
-AppVersion 5008002
+AppVersion 5008003
AppVersionStrMajor 5.8
-AppVersionStrSmall 5.8.2
-AppVersionStr 5.8.2
+AppVersionStrSmall 5.8.3
+AppVersionStr 5.8.3
BetaChannel 0
AlphaVersion 0
-AppVersionOriginal 5.8.2
+AppVersionOriginal 5.8.3
diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake
index ec75a29e1..eb3287cd5 100644
--- a/Telegram/cmake/td_ui.cmake
+++ b/Telegram/cmake/td_ui.cmake
@@ -21,6 +21,7 @@ set(style_files
ui/chat/chat.style
ui/effects/credits.style
ui/effects/premium.style
+ ui/color_indices.style
boxes/boxes.style
dialogs/dialogs.style
chat_helpers/chat_helpers.style
diff --git a/changelog.txt b/changelog.txt
index a6704ef0b..22c02f612 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -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)
- Improve bottom label color in mini apps.