Merge tag 'v5.8.3' into dev

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

View file

@ -374,6 +374,8 @@ PRIVATE
boxes/peer_list_box.h
boxes/peer_list_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

View file

@ -290,6 +290,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_error_cant_add_admin_invite" = "You can't add this user as an admin because they are not a member of this group and you are not allowed to add them.";
"lng_error_cant_add_admin_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";

View file

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

View file

@ -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"

View file

@ -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"

View file

@ -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<Api::CreditsHistory>(
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) {

View file

@ -133,17 +133,26 @@ constexpr auto kTransactionsLimit = 100;
}
[[nodiscard]] Data::SubscriptionEntry SubscriptionFromTL(
const MTPStarsSubscription &tl) {
const MTPStarsSubscription &tl,
not_null<PeerData*> 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<Main::Session*> session,
const QString &id,
bool cancel,
Fn<void()> done,
Fn<void(QString)> fail) {
using Flag = MTPpayments_ChangeStarsSubscription::Flag;
session->api().request(
MTPpayments_ChangeStarsSubscription(
MTP_flags(Flag::f_canceled),
MTP_inputPeerSelf(),
MTP_string(id),
MTP_bool(cancel)
)).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send();
}
} // namespace Api

View file

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

View file

@ -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);

View file

@ -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*> 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));
}
}

View file

@ -1247,25 +1247,20 @@ void AddCreditsHistoryEntryTable(
}));
}
}
if (!entry.subscriptionUntil.isNull() && !entry.title.isEmpty()) {
AddTableRow(
table,
tr::lng_gift_link_label_reason(),
tr::lng_credits_box_history_entry_subscription(
Ui::Text::WithEntities));
}
if (!entry.id.isEmpty()) {
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<Ui::FlatLabel>(
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);

View file

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

View file

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

View file

@ -26,17 +26,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_credits_graphics.h"
#include "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<Ui::BoxContent*> box,
not_null<Ui::RpWidget*> button,
const style::Box &stBox) {
const auto terms = Ui::CreateChild<Ui::FlatLabel>(
button->parentWidget(),
tr::lng_channel_invite_subscription_terms(
lt_link,
rpl::combine(
tr::lng_paid_react_agree_link(),
tr::lng_group_invite_subscription_about_url()
) | rpl::map([](const QString &text, const QString &url) {
return Ui::Text::Link(text, url);
}),
Ui::Text::RichLangValue),
st::inviteLinkSubscribeBoxTerms);
const auto &buttonPadding = stBox.buttonPadding;
const auto style = box->lifetime().make_state<style::Box>(style::Box{
.buttonPadding = buttonPadding + QMargins(0, 0, 0, terms->height()),
.buttonHeight = stBox.buttonHeight,
.button = stBox.button,
.margin = stBox.margin,
.title = stBox.title,
.bg = stBox.bg,
.titleAdditionalFg = stBox.titleAdditionalFg,
.shadowIgnoreTopSkip = stBox.shadowIgnoreTopSkip,
.shadowIgnoreBottomSkip = stBox.shadowIgnoreBottomSkip,
});
button->geometryValue() | rpl::start_with_next([=](const QRect &rect) {
terms->resizeToWidth(box->width()
- rect::m::sum::h(st::boxRowPadding));
terms->moveToLeft(
rect.x() + (rect.width() - terms->width()) / 2,
rect::bottom(rect) + buttonPadding.bottom() / 2);
}, terms->lifetime());
box->setStyle(*style);
}
[[nodiscard]] rpl::producer<TextWithEntities> SendCreditsConfirmText(
not_null<Main::Session*> session,
not_null<Payments::CreditsFormData*> 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<Ui::RpWidget*> SendCreditsBadge(
not_null<Ui::RpWidget*> parent,
int credits) {
const auto widget = Ui::CreateChild<Ui::RpWidget>(parent);
const auto &font = st::chatGiveawayBadgeFont;
const auto text = QString::number(credits);
const auto iconHeight = font->ascent - font->descent;
const auto iconWidth = iconHeight + st::lineWidth;
const auto width = font->width(text) + iconWidth + st::lineWidth;
const auto inner = QRect(0, 0, width, font->height);
const auto rect = inner + st::subscriptionCreditsBadgePadding;
const auto size = rect.size();
const auto svg = widget->lifetime().make_state<QSvgRenderer>(
Ui::Premium::Svg());
const auto half = st::chatGiveawayBadgeStroke / 2.;
const auto left = st::subscriptionCreditsBadgePadding.left();
const auto smaller = QRectF(rect.translated(-rect.topLeft()))
- Margins(half);
const auto radius = smaller.height() / 2.;
widget->resize(size);
widget->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(widget);
auto hq = PainterHighQualityEnabler(p);
p.setPen(QPen(st::premiumButtonFg, st::chatGiveawayBadgeStroke * 1.));
p.setBrush(st::creditsBg3);
p.drawRoundedRect(smaller, radius, radius);
p.translate(0, font->descent / 2);
p.setPen(st::premiumButtonFg);
p.setBrush(st::premiumButtonFg);
svg->render(
&p,
QRect(
left,
half + (inner.height() - iconHeight) / 2,
iconHeight,
iconHeight));
p.setFont(font);
p.drawText(
left + iconWidth,
st::subscriptionCreditsBadgePadding.top() + font->ascent,
text);
}, widget->lifetime());
return widget;
}
} // namespace
void SendCreditsBox(
@ -203,7 +309,8 @@ void SendCreditsBox(
rpl::variable<bool> confirmButtonBusy = false;
};
const auto state = box->lifetime().make_state<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<Ui::CenterWrap<>>(
box,
object_ptr<Ui::FlatLabel>(
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<Ui::CenterWrap<>>(
box,
Ui::CreatePeerBubble(box, bot)));
Ui::AddSkip(content);
}
Ui::AddSkip(content);
box->addRow(object_ptr<Ui::CenterWrap<>>(
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<Ui::IconButton>(
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());
}

View file

@ -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 {

View file

@ -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<Ui::CallButton>(
widget(),
st::callStartVideo);
_startVideo->show();
_startVideo->setText(tr::lng_call_start_video());
_startVideo->clicks() | rpl::map_to(true) | rpl::start_to_stream(
_startOutgoingRequests,

View file

@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto 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;

View file

@ -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>();
_creditsSubsRebuilder = result;
return result;
}
Session::CreditsSubsRebuilderPtr Session::activeCreditsSubsRebuilder() const {
return _creditsSubsRebuilder.lock();
}
void Session::registerHeavyViewPart(not_null<ViewElement*> view) {
_heavyViewParts.emplace(view);
}

View file

@ -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<Data::CreditsStatusSlice>;
using CreditsSubsRebuilderPtr = std::shared_ptr<CreditsSubsRebuilder>;
[[nodiscard]] CreditsSubsRebuilderPtr createCreditsSubsRebuilder();
[[nodiscard]] CreditsSubsRebuilderPtr activeCreditsSubsRebuilder() const;
void registerRestricted(
not_null<const HistoryItem*> item,
const QString &reason);
@ -1095,6 +1101,8 @@ private:
MessageIdsList _mimeForwardIds;
std::weak_ptr<CreditsSubsRebuilder> _creditsSubsRebuilder;
using CredentialsWithGeneration = std::pair<
const Passport::SavedCredentials,
int>;

View file

@ -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;
};

View file

@ -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());

View file

@ -130,19 +130,6 @@ int BinarySearchBlocksOrItems(const T &list, int edge) {
return start;
}
[[nodiscard]] bool CanSendReply(not_null<const HistoryItem*> item) {
if (item->isDeleted()) {
return false;
}
const auto peer = item->history()->peer;
const auto topic = item->topic();
return topic
? Data::CanSendAnything(topic)
: (Data::CanSendAnything(peer)
&& (!peer->isChannel() || peer->asChannel()->amIn()));
}
} // namespace
// 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<HistoryMainElementDelegateMixin> {
return std::make_unique<HistoryMainElementDelegate>();
}
bool CanSendReply(not_null<const HistoryItem*> item) {
if (item->isDeleted()) {
return false;
}
const auto peer = item->history()->peer;
const auto topic = item->topic();
return topic
? Data::CanSendAnything(topic)
: (Data::CanSendAnything(peer)
&& (!peer->isChannel() || peer->asChannel()->amIn()));
}

View file

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

View file

@ -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<MTPDmessageActionPaymentSentMe>,
preparePaymentSentMe,
PrepareErrorText<MTPDmessageActionSecureValuesSentMe>,
prepareGroupCall,
prepareInviteToGroupCall,

View file

@ -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<Data::Draft>(
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));
}
}
}

View file

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

View file

@ -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));

View file

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

View file

@ -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);

View file

@ -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<Giveaway::GiveawayTypeRow>(
box,
GiveawayType::Credits,
kColorIndexCredits,
st::colorIndexOrange,
tr::lng_credits_summary_title(),
tr::lng_giveaway_create_subtitle(),
std::move(badge)));

View file

@ -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<Ui::RpWidget*> 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)

View file

@ -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(

View file

@ -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<Ui::CenterWrap<>>(
box,
object_ptr<Ui::RpWidget>(box)))->entity();
peerBubble->setAttribute(
Qt::WA_TransparentForMouseEvents);
const auto left = Ui::CreateChild<Ui::UserpicButton>(
peerBubble,
peer,
st::uploadUserpicButton);
const auto right = Ui::CreateChild<Ui::FlatLabel>(
peerBubble,
Info::Profile::NameValue(peer),
st::channelEarnSemiboldLabel);
rpl::combine(
left->sizeValue(),
right->sizeValue()
) | rpl::start_with_next([=](
const QSize &leftSize,
const QSize &rightSize) {
const auto padding = QMargins(
st::chatGiveawayPeerPadding.left() * 2,
st::chatGiveawayPeerPadding.top(),
st::chatGiveawayPeerPadding.right(),
st::chatGiveawayPeerPadding.bottom());
peerBubble->resize(
leftSize.width()
+ rightSize.width()
+ rect::m::sum::h(padding),
leftSize.height());
left->moveToLeft(0, 0);
right->moveToRight(
padding.right(),
padding.top());
const auto maxRightSize = box->width()
- rect::m::sum::h(st::boxRowPadding)
- rect::m::sum::h(padding)
- leftSize.width();
if (rightSize.width() > maxRightSize) {
right->resizeToWidth(maxRightSize);
}
}, peerBubble->lifetime());
peerBubble->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(peerBubble);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::windowBgOver);
const auto rect = peerBubble->rect();
const auto radius = rect.height() / 2;
p.drawRoundedRect(rect, radius, radius);
}, peerBubble->lifetime());
Ui::CreatePeerBubble(box, peer)));
}
const auto closeBox = [=] { box->closeBox(); };
{

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_credits.h"
#include "api/api_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<void(const Data::Boost &)>;
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<Ui::SettingsButton> CreateShowMoreButton(
not_null<Ui::RpWidget*> parent,
rpl::producer<QString> title) {
auto owned = object_ptr<Ui::SettingsButton>(
parent,
std::move(title),
st::statisticsShowMoreButton);
Ui::AddToggleUpDownArrowToMoreButton(owned.data());
return owned;
}
[[nodiscard]] QString FormatText(
int value1, tr::phrase<lngtag_count> phrase1,
int value2, tr::phrase<lngtag_count> 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<Ui::DynamicImage> _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<PeerListRow*> row) {
delegate()->peerListUpdateRow(row);
},
@ -1239,28 +1358,36 @@ void AddCreditsHistoryList(
not_null<PeerData*> bot,
bool in,
bool out,
bool subscription) {
bool subs) {
struct State final {
State(
CreditsDescriptor d,
std::shared_ptr<Main::SessionShow> show)
: delegate(std::move(show))
, controller(std::move(d)) {
State(CreditsDescriptor d) : controller(std::move(d)) {
}
PeerListContentDelegateShow delegate;
std::optional<PeerListContentDelegateShow> creditsDelegate;
std::optional<PeerListWidgetsDelegate> subscriptionDelegate;
CreditsController controller;
};
const auto state = container->lifetime().make_state<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<PeerListWidgets>(container, &state->controller)));
state->controller.setDelegate(&(*state->subscriptionDelegate));
} else {
state->creditsDelegate.emplace(show);
state->creditsDelegate->setContent(container->add(
object_ptr<PeerListContent>(container, &state->controller)));
state->controller.setDelegate(&(*state->creditsDelegate));
}
state->delegate.setContent(container->add(
object_ptr<PeerListContent>(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<Ui::SlideWrap<Ui::SettingsButton>>(
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<Ui::SlideWrap<Ui::SettingsButton>*> AddShowMoreButton(
not_null<Ui::VerticalLayout*> container,
rpl::producer<QString> title) {
const auto wrap = container->add(
return container->add(
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
container,
object_ptr<Ui::SettingsButton>(
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

View file

@ -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<void()> done) {
void WebViewInstance::confirmAppOpen(
bool writeAccess,
Fn<void(bool allowWrite)> done) {
Fn<void(bool allowWrite)> done,
bool forceConfirmation) {
if (!forceConfirmation
&& (_bot->isVerified()
|| _session->local().isBotTrustedOpenWebView(_bot->id))) {
done(writeAccess);
return;
}
_parentShow->show(Box([=](not_null<Ui::GenericBox*> box) {
const auto allowed = std::make_shared<Ui::Checkbox*>();
const auto callback = [=](Fn<void()> close) {
_session->local().markBotTrustedOpenWebView(_bot->id);
done((*allowed) && (*allowed)->checked());
close();
};

View file

@ -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<void()> done);
void confirmAppOpen(bool writeAccess, Fn<void(bool allowWrite)> done);
void confirmAppOpen(
bool writeAccess,
Fn<void(bool allowWrite)> done,
bool forceConfirmation);
struct ShowArgs {
QString url;

View file

@ -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,

View file

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

View file

@ -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) {

View file

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

View file

@ -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<Ui::VerticalLayout*> content,
not_null<Ui::RpWidget*> widget,
const style::UserpicButton &stUser,
int boxWidth,
float64 heightRatio) {
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
widget,
false,
Ui::Premium::MiniStars::Type::BiStars);
stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
widget->resize(
boxWidth - stUser.photoSize,
stUser.photoSize * heightRatio);
content->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
widget->moveToLeft(stUser.photoSize / 2, 0);
const auto starsRect = Rect(widget->size());
stars->setPosition(starsRect.topLeft());
stars->setSize(starsRect.size());
widget->lower();
}, widget->lifetime());
widget->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
auto p = QPainter(widget);
p.fillRect(r, Qt::transparent);
stars->paint(p);
}, widget->lifetime());
}
} // namespace
SubscriptionRightLabel PaintSubscriptionRightLabelCallback(
@ -664,31 +696,12 @@ void BoostCreditsBox(
{
const auto &stUser = st::premiumGiftsUserpicButton;
const auto widget = content->add(object_ptr<Ui::RpWidget>(content));
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
widget,
false,
Ui::Premium::MiniStars::Type::BiStars);
stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
widget->resize(
st::boxWidth - stUser.photoSize,
stUser.photoSize * 1.3);
AddMiniStars(content, widget, stUser, st::boxWidth, 1.3);
const auto svg = std::make_shared<QSvgRenderer>(
Ui::Premium::ColorizedSvg(
Ui::Premium::CreditsIconGradientStops()));
content->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
widget->moveToLeft(stUser.photoSize / 2, 0);
const auto starsRect = Rect(widget->size());
stars->setPosition(starsRect.topLeft());
stars->setSize(starsRect.size());
widget->lower();
}, widget->lifetime());
widget->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
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<Ui::GenericBox> weak,
not_null<Main::Session*> session) {
const auto rebuilder = session->data().activeCreditsSubsRebuilder();
if (const auto strong = weak.data()) {
if (!rebuilder) {
return strong->closeBox();
}
const auto api
= strong->lifetime().make_state<Api::CreditsHistory>(
session->user(),
true,
true);
api->requestSubscriptions({}, [=](Data::CreditsStatusSlice first) {
rebuilder->fire(std::move(first));
if (const auto strong = weak.data()) {
strong->closeBox();
}
});
}
}
void ReceiptCreditsBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> 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<Ui::RpWidget>(content);
AddMiniStars(content, widget, stUser, st::boxWideWidth, 1.5);
}
const auto photoId = s.photoId ? s.photoId : e.photoId;
const auto callback = [=](Fn<void()> update) {
return Ui::GenerateCreditsPaintEntryCallback(
session->data().photo(photoId),
std::move(update));
};
content->add(object_ptr<Ui::CenterWrap<>>(
content,
GenericEntryPhoto(content, callback, stUser.photoSize)));
} else if (peer && !e.gift) {
if (e.subscriptionUntil.isNull() && s.until.isNull()) {
content->add(object_ptr<Ui::CenterWrap<>>(
@ -950,11 +1000,13 @@ void ReceiptCreditsBox(
box,
object_ptr<Ui::FlatLabel>(
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<Ui::FlatLabel>(
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<Ui::RpWidget>(content);
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
widget,
false,
Ui::Premium::MiniStars::Type::BiStars);
stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
widget->resize(
st::boxWidth - stUser.photoSize,
stUser.photoSize * 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<Ui::IconButton>(
content,
st::boxTitleClose);
close->setClickedCallback([=] { box->closeBox(); });
content->widthValue() | rpl::start_with_next([=](int) {
close->moveToRight(0, 0);
}, content->lifetime());
}
const auto button = box->addButton(std::move(confirmText), [=] {
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([=] {

View file

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

View file

@ -156,3 +156,20 @@ giftListAbout: FlatLabel(defaultFlatLabel) {
giftListAboutMargin: margins(12px, 24px, 12px, 24px);
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);

View file

@ -533,7 +533,7 @@ Fn<PaintRoundImageCallback(Fn<void()>)> 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<void()> update) {
return GenerateCreditsPaintEntryCallback(

View file

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

View file

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

View file

@ -293,15 +293,18 @@ not_null<Ui::RpWidget*> AddChatFiltersTabsStrip(
if ((list.size() <= 1 && !slider->width()) || state->ignoreRefresh) {
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();

View file

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

View file

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

View file

@ -281,7 +281,7 @@ private:
void addInfo();
void 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<Ui::BoxContent> ShowForwardMessagesBox(
class ListBox final : public PeerListBox {
public:
using PeerListBox::PeerListBox;
ListBox(
QWidget *parent,
std::unique_ptr<PeerListController> controller,
Fn<void(not_null<ListBox*>)> init)
: PeerListBox(
parent,
std::move(controller),
[=](not_null<PeerListBox*> box) {
init(static_cast<ListBox*>(box.get()));
}) {
}
void setBottomSkip(int bottomSkip) {
PeerListBox::setInnerBottomSkip(bottomSkip);
@ -2095,9 +2110,21 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
_forwardOptions = forwardOptions;
}
not_null<PeerListContent*> 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<Ui::BoxContent> ShowForwardMessagesBox(
}) {
}
std::unique_ptr<PeerListRow> createRestoredRow(
not_null<PeerData*> peer) override final {
return ChooseRecipientBoxController::createRow(
peer->owner().history(peer));
}
using PeerListController::setSearchNoResultsText;
void rowClicked(not_null<PeerListRow*> row) override final {
@ -2167,166 +2200,48 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
base::unique_qptr<Ui::PopupMenu> menu;
};
const auto applyFilter = [=](not_null<PeerListBox*> box, FilterId id) {
const auto applyFilter = [=](not_null<ListBox*> box, FilterId id) {
box->scrollToY(0);
auto &filters = session->data().chatsFilters();
const auto &list = filters.list();
if (list.size() <= 1) {
return;
}
const auto pinnedList = [&](
not_null<Dialogs::MainList*> list,
bool foundSelf) {
const auto pinned = list->pinned()->order();
auto peers = std::vector<not_null<PeerData*>>();
peers.reserve(pinned.size());
for (const auto &pin : pinned) {
if (!foundSelf && pin.peer()->isSelf()) {
peers.insert(peers.begin(), pin.peer());
foundSelf = true;
} else {
peers.push_back(pin.peer());
}
}
if (!foundSelf) {
peers.insert(peers.begin(), session->user());
}
return peers;
};
const auto folder = session->data().folderLoaded(
Data::Folder::kId);
const auto pinned = pinnedList(
id
? filters.chatsList(id)
: session->data().chatsList(nullptr),
!!id);
const auto pinnedInFolder = (!id && folder)
? pinnedList(folder->chatsList(), true)
: std::vector<not_null<PeerData*>>();
box->peerListSortRows([&](
const PeerListRow &r1,
const PeerListRow &r2) {
{ // Pinned to top.
auto it1 = pinned.end();
auto it2 = pinned.end();
for (auto it = pinned.begin(); it != pinned.end(); ++it) {
if ((*it) == r1.peer()) {
it1 = it;
}
if ((*it) == r2.peer()) {
it2 = it;
}
if (it1 != pinned.end() && it2 != pinned.end()) {
break;
}
}
if (it1 == pinned.end() && it2 != pinned.end()) {
return false;
} else if (it2 == pinned.end() && it1 != pinned.end()) {
return true;
} else if (it1 != pinned.end() && it2 != pinned.end()) {
return it1 < it2;
}
}
{ // Pinned to bottom.
const auto &indexed = session->data().contactsNoChatsList();
auto it1 = indexed->end();
auto it2 = indexed->end();
for (auto it = indexed->begin(); it != indexed->end(); ++it) {
if (it->get()->key().peer() == r1.peer()) {
it1 = it;
}
if (it->get()->key().peer() == r2.peer()) {
it2 = it;
}
if (it1 != indexed->end() && it2 != indexed->end()) {
break;
}
}
if (it1 == indexed->end() && it2 != indexed->end()) {
return true;
} else if (it2 == indexed->end() && it1 != indexed->end()) {
return false;
} else if (it1 != indexed->end() && it2 != indexed->end()) {
return it1 > it2;
}
}
if (folder) {
const auto pinned1 = ranges::find(pinnedInFolder, r1.peer());
const auto pinned2 = ranges::find(pinnedInFolder, r2.peer());
const auto isPinned1 = pinned1 != pinnedInFolder.end();
const auto isPinned2 = pinned2 != pinnedInFolder.end();
if (isPinned1 && isPinned2) {
return pinned1 < pinned2;
}
const auto &indexed = folder->chatsList()->indexed();
auto it1 = indexed->end();
auto it2 = indexed->end();
for (auto it = indexed->begin(); it != indexed->end(); ++it) {
if (it->get()->key().peer() == r1.peer()) {
it1 = it;
}
if (it->get()->key().peer() == r2.peer()) {
it2 = it;
}
if (it1 != indexed->end() && it2 != indexed->end()) {
break;
}
}
const auto isFoldered1 = it1 != indexed->end();
const auto isFoldered2 = it2 != indexed->end();
if (isPinned1 && !isPinned2) {
return isFoldered2;
}
if (isPinned2 && !isPinned1) {
return !isFoldered1;
}
if (!isPinned1 && !isPinned2) {
if (!isFoldered1 && isFoldered2) {
return true;
} else if (!isFoldered2 && isFoldered1) {
return false;
} else if (isFoldered1 && isFoldered2) {
return it1 < it2;
}
}
}
const auto history1 = session->data().history(r1.peer());
const auto history2 = session->data().history(r2.peer());
const auto date1 = history1->lastMessage()
? history1->lastMessage()->date()
: TimeId(0);
const auto date2 = history2->lastMessage()
? history2->lastMessage()->date()
: TimeId(0);
return date1 > date2;
});
const auto filter = ranges::find(
list,
id,
&Data::ChatFilter::id);
if (filter == list.end()) {
if (box->filterId() == id) {
return;
}
box->peerListPartitionRows([&](const PeerListRow &row) {
const auto rowPtr = const_cast<PeerListRow*>(&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<PeerListState>();
state->controllerState = std::make_unique<SavedState>();
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<Controller>(session);
const auto controllerRaw = controller.get();
auto init = [=](not_null<PeerListBox*> box) {
auto init = [=](not_null<ListBox*> box) {
controllerRaw->setSearchNoResultsText(
tr::lng_bot_chats_not_found(tr::now));
box->setSpecialTabMode(true);

View file

@ -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<Data::Draft>(
textWithTags,
to.currentReplyTo,

View file

@ -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

View file

@ -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

View file

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