diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 82f1089f8..55143efd2 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -374,6 +374,8 @@ PRIVATE boxes/peer_list_box.h boxes/peer_list_controllers.cpp boxes/peer_list_controllers.h + boxes/peer_list_widgets.cpp + boxes/peer_list_widgets.h boxes/peer_lists_box.cpp boxes/peer_lists_box.h boxes/passcode_box.cpp @@ -1631,6 +1633,8 @@ PRIVATE ui/widgets/label_with_custom_emoji.h ui/widgets/chat_filters_tabs_strip.cpp ui/widgets/chat_filters_tabs_strip.h + ui/widgets/peer_bubble.cpp + ui/widgets/peer_bubble.h ui/countryinput.cpp ui/countryinput.h ui/dynamic_thumbnails.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 9be66d4ae..19c05d3eb 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -290,6 +290,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_error_cant_add_admin_invite" = "You can't add this user as an admin because they are not a member of this group and you are not allowed to add them."; "lng_error_cant_add_admin_unban" = "Sorry, you can't add this user as an admin because they are in the Removed Users list and you can't unban them."; "lng_error_cant_ban_admin" = "You can't ban this user because they are an admin in this group and you are not allowed to demote them."; +"lng_error_cant_reply_other" = "This message can't be replied in another chat."; "lng_error_admin_limit" = "Sorry, you've reached the maximum number of admins for this group."; "lng_error_admin_limit_channel" = "Sorry, you've reached the maximum number of admins for this channel."; "lng_error_post_link_invalid" = "Unfortunately, you can't access this message. You aren't a member of the chat where it was posted."; @@ -298,6 +299,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_error_nocopy_group" = "Sorry, copying from this group is disabled by admins."; "lng_error_nocopy_channel" = "Sorry, copying from this channel is disabled by admins."; "lng_error_nocopy_story" = "Sorry, the creator of this story disabled copying."; +"lng_error_schedule_limit" = "Sorry, you can't schedule more than 100 messages."; "lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?"; "lng_sure_add_admin_invite_channel" = "This user is not a subscriber of this channel. Add them to the channel and promote them to admin?"; "lng_sure_add_admin_unremove" = "This user is currently restricted or removed. Are you sure you want to promote them?"; @@ -1861,6 +1863,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_payment_init_recurring_for" = "You successfully transferred {amount} to {user} for {invoice} and allowed future recurring payments"; "lng_action_payment_init_recurring" = "You successfully transferred {amount} to {user} and allowed future recurring payments"; "lng_action_payment_used_recurring" = "You were charged {amount} via recurring payment"; +"lng_action_payment_bot_done" = "Bot connected to this account received {amount}"; +"lng_action_payment_bot_recurring" = "Bot connected to this account received {amount} via recurring payment"; "lng_action_took_screenshot" = "{from} took a screenshot!"; "lng_action_you_took_screenshot" = "You took a screenshot!"; "lng_action_bot_allowed_from_domain" = "You allowed this bot to message you when you logged in on {domain}."; @@ -2444,6 +2448,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_box_out_media#other" = "Do you want to unlock {media} in {chat} for **{count} Stars**?"; "lng_credits_box_out_media_user#one" = "Do you want to unlock {media} from {user} for **{count} Star**?"; "lng_credits_box_out_media_user#other" = "Do you want to unlock {media} from {user} for **{count} Stars**?"; +"lng_credits_box_out_subscription_bot#one" = "Do you want to subscribe to **{title}** in **{recipient}** for **{count}** star per month?"; +"lng_credits_box_out_subscription_bot#other" = "Do you want to subscribe to **{title}** in **{recipient}** for **{count}** stars per month?"; +"lng_credits_box_out_subscription_business#one" = "Do you want to subscribe to **{title}** from **{recipient}** for **{count}** star per month?"; +"lng_credits_box_out_subscription_business#other" = "Do you want to subscribe to **{title}** from **{recipient}** for **{count}** stars per month?"; +"lng_credits_box_out_subscription_confirm#one" = "Subscribe for {emoji} {count} / month"; +"lng_credits_box_out_subscription_confirm#other" = "Subscribe for {emoji} {count} / month"; "lng_credits_box_out_photo" = "a photo"; "lng_credits_box_out_photos#one" = "{count} photo"; "lng_credits_box_out_photos#other" = "{count} photos"; @@ -2504,6 +2514,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_subscriber_subtitle" = "appx. {total} per month"; "lng_credits_subscription_row_to" = "Subscription"; +"lng_credits_subscription_row_to_bot" = "Bot"; +"lng_credits_subscription_row_to_business" = "Business"; "lng_credits_subscription_row_from" = "Subscribed"; "lng_credits_subscription_row_next_on" = "Renews"; @@ -2514,13 +2526,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_subscription_on_about" = "If you cancel now, you will still be able to access your subscription until {date}."; "lng_credits_subscription_off_button" = "Renew Subscription"; +"lng_credits_subscription_off_rejoin_button" = "Subscribe again"; "lng_credits_subscription_off_about" = "You have canceled your subscription."; +"lng_credits_subscription_off_by_bot_about" = "{bot} has canceled your subscription."; "lng_credits_subscription_status_on" = "renews on {date}"; "lng_credits_subscription_status_off" = "expires on {date}"; "lng_credits_subscription_status_none" = "expired on {date}"; "lng_credits_subscription_status_off_right" = "canceled"; "lng_credits_subscription_status_none_right" = "expired"; +"lng_credits_subscription_status_off_by_bot_right" = "canceled\nby bot"; "lng_credits_small_balance_title#one" = "{count} Star Needed"; "lng_credits_small_balance_title#other" = "{count} Stars Needed"; diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 8d93b797d..1f5ac3f65 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.8.3.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 4f0000cc7..38113d8cc 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,8,2,0 - PRODUCTVERSION 5,8,2,0 + FILEVERSION 5,8,3,0 + PRODUCTVERSION 5,8,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop" - VALUE "FileVersion", "5.8.2.0" + VALUE "FileVersion", "5.8.3.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "5.8.2.0" + VALUE "ProductVersion", "5.8.3.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 3cb7b8d82..7a45674d7 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,8,2,0 - PRODUCTVERSION 5,8,2,0 + FILEVERSION 5,8,3,0 + PRODUCTVERSION 5,8,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop Updater" - VALUE "FileVersion", "5.8.2.0" + VALUE "FileVersion", "5.8.3.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "5.8.2.0" + VALUE "ProductVersion", "5.8.3.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/api/api_chat_invite.cpp b/Telegram/SourceFiles/api/api_chat_invite.cpp index 0a53c61dd..134e3a005 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.cpp +++ b/Telegram/SourceFiles/api/api_chat_invite.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_invite.h" #include "apiwrap.h" +#include "api/api_credits.h" #include "boxes/premium_limits_box.h" #include "core/application.h" #include "data/components/credits.h" @@ -295,20 +296,39 @@ void ConfirmSubscriptionBox( const auto buttonWidth = state->saveButton ? state->saveButton->width() : 0; + const auto finish = [=] { + state->api = std::nullopt; + state->loading.force_assign(false); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }; state->api->request( MTPpayments_SendStarsForm( MTP_long(formId), MTP_inputInvoiceChatInviteSubscription(MTP_string(hash))) ).done([=](const MTPpayments_PaymentResult &result) { - state->api = std::nullopt; - state->loading.force_assign(false); result.match([&](const MTPDpayments_paymentResult &data) { session->api().applyUpdates(data.vupdates()); }, [](const MTPDpayments_paymentVerificationNeeded &data) { }); - if (weak) { - box->closeBox(); + const auto refill = session->data().activeCreditsSubsRebuilder(); + const auto strong = weak.data(); + if (!strong) { + return; } + if (!refill) { + return finish(); + } + const auto api + = strong->lifetime().make_state( + session->user(), + true, + true); + api->requestSubscriptions({}, [=](Data::CreditsStatusSlice d) { + refill->fire(std::move(d)); + finish(); + }); }).fail([=](const MTP::Error &error) { const auto id = error.type(); if (weak) { diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 80e995219..175112c40 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -133,17 +133,26 @@ constexpr auto kTransactionsLimit = 100; } [[nodiscard]] Data::SubscriptionEntry SubscriptionFromTL( - const MTPStarsSubscription &tl) { + const MTPStarsSubscription &tl, + not_null peer) { return Data::SubscriptionEntry{ .id = qs(tl.data().vid()), .inviteHash = qs(tl.data().vchat_invite_hash().value_or_empty()), + .title = qs(tl.data().vtitle().value_or_empty()), + .slug = qs(tl.data().vinvoice_slug().value_or_empty()), .until = base::unixtime::parse(tl.data().vuntil_date().v), .subscription = Data::PeerSubscription{ .credits = tl.data().vpricing().data().vamount().v, .period = tl.data().vpricing().data().vperiod().v, }, .barePeerId = peerFromMTP(tl.data().vpeer()).value, + .photoId = (tl.data().vphoto() + ? peer->owner().photoFromWeb( + *tl.data().vphoto(), + ImageLocation())->id + : 0), .cancelled = tl.data().is_canceled(), + .cancelledByBot = tl.data().is_bot_canceled(), .expired = (base::unixtime::now() > tl.data().vuntil_date().v), .canRefulfill = tl.data().is_can_refulfill(), }; @@ -166,7 +175,7 @@ constexpr auto kTransactionsLimit = 100; if (const auto history = data.vsubscriptions()) { subscriptions.reserve(history->v.size()); for (const auto &tl : history->v) { - subscriptions.push_back(SubscriptionFromTL(tl)); + subscriptions.push_back(SubscriptionFromTL(tl, peer)); } } return Data::CreditsStatusSlice{ @@ -463,4 +472,20 @@ Data::CreditsGiveawayOptions CreditsGiveawayOptions::options() const { return _options; } +void EditCreditsSubscription( + not_null session, + const QString &id, + bool cancel, + Fn done, + Fn fail) { + using Flag = MTPpayments_ChangeStarsSubscription::Flag; + session->api().request( + MTPpayments_ChangeStarsSubscription( + MTP_flags(Flag::f_canceled), + MTP_inputPeerSelf(), + MTP_string(id), + MTP_bool(cancel) + )).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send(); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h index e178cb866..4f697cf09 100644 --- a/Telegram/SourceFiles/api/api_credits.h +++ b/Telegram/SourceFiles/api/api_credits.h @@ -109,4 +109,11 @@ private: [[nodiscard]] rpl::producer> PremiumPeerBot( not_null session); +void EditCreditsSubscription( + not_null session, + const QString &id, + bool cancel, + Fn done, + Fn fail); + } // namespace Api diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 6ffccae96..22bf7cd0b 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -582,6 +582,14 @@ void ApiWrap::sendMessageFail( : tr::lng_error_noforwards_group(tr::now), kJoinErrorDuration); } else if (error == u"PREMIUM_ACCOUNT_REQUIRED"_q) { Settings::ShowPremium(&session(), "premium_stickers"); + } else if (error == u"SCHEDULE_TOO_MUCH"_q) { + auto &scheduled = _session->scheduledMessages(); + if (const auto item = scheduled.lookupItem(peer->id, itemId.msg)) { + scheduled.removeSending(item); + } + if (show) { + show->showToast(tr::lng_error_schedule_limit(tr::now)); + } } if (const auto item = _session->data().message(itemId)) { Assert(randomId != 0); diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp index ff0226a87..ce1418618 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/filters/edit_filter_chats_list.h" +#include "data/data_chat_filters.h" #include "data/data_premium_limits.h" +#include "data/data_session.h" #include "history/history.h" #include "window/window_session_controller.h" #include "lang/lang_keys.h" @@ -125,7 +127,15 @@ Flag TypeRow::flag() const { } ExceptionRow::ExceptionRow(not_null history) : Row(history) { - if (peer()->isSelf()) { + auto filters = QStringList(); + for (const auto &filter : history->owner().chatsFilters().list()) { + if (filter.contains(history) && filter.id()) { + filters << filter.title(); + } + } + if (!filters.isEmpty()) { + setCustomStatus(filters.join(", ")); + } else if (peer()->isSelf()) { setCustomStatus(tr::lng_saved_forward_here(tr::now)); } } diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index a3d385c72..542189659 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -1247,25 +1247,20 @@ void AddCreditsHistoryEntryTable( })); } } + if (!entry.subscriptionUntil.isNull() && !entry.title.isEmpty()) { + AddTableRow( + table, + tr::lng_gift_link_label_reason(), + tr::lng_credits_box_history_entry_subscription( + Ui::Text::WithEntities)); + } if (!entry.id.isEmpty()) { - constexpr auto kOneLineCount = 22; - const auto oneLine = entry.id.size() <= kOneLineCount; - auto multiLine = QString(); - if (!oneLine) { - for (auto i = 0; i < entry.id.size(); ++i) { - multiLine.append(entry.id[i]); - if ((i + 1) % kOneLineCount == 0) { - multiLine.append('\n'); - } - } - } + constexpr auto kOneLineCount = 24; + const auto oneLine = entry.id.length() <= kOneLineCount; auto label = object_ptr( table, rpl::single( - Ui::Text::Wrapped( - { oneLine ? entry.id : std::move(multiLine) }, - EntityType::Code, - {})), + Ui::Text::Wrapped({ entry.id }, EntityType::Code, {})), oneLine ? st::giveawayGiftCodeValue : st::giveawayGiftCodeValueMultiline); @@ -1324,11 +1319,24 @@ void AddSubscriptionEntryTable( st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); const auto peerId = PeerId(s.barePeerId); + const auto user = peerIsUser(peerId) + ? controller->session().data().peer(peerId)->asUser() + : nullptr; AddTableRow( table, - tr::lng_credits_subscription_row_to(), + (!s.title.isEmpty() && user && user->botInfo) + ? tr::lng_credits_subscription_row_to_bot() + : (!s.title.isEmpty() && user && !user->botInfo) + ? tr::lng_credits_subscription_row_to_business() + : tr::lng_credits_subscription_row_to(), controller, peerId); + if (!s.title.isEmpty()) { + AddTableRow( + table, + tr::lng_credits_subscription_row_to(), + rpl::single(Ui::Text::WithEntities(s.title))); + } if (!s.until.isNull()) { if (s.subscription.period > 0) { const auto subscribed = s.until.addSecs(-s.subscription.period); diff --git a/Telegram/SourceFiles/boxes/peer_list_widgets.cpp b/Telegram/SourceFiles/boxes/peer_list_widgets.cpp new file mode 100644 index 000000000..dfa729eb6 --- /dev/null +++ b/Telegram/SourceFiles/boxes/peer_list_widgets.cpp @@ -0,0 +1,388 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "boxes/peer_list_widgets.h" + +#include "ui/painter.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/vertical_layout.h" +#include "styles/style_boxes.h" + +namespace { + +using State = std::unique_ptr; + +} // namespace + +PeerListWidgets::PeerListWidgets( + not_null parent, + not_null controller) +: Ui::RpWidget(parent) +, _controller(controller) +, _st(controller->computeListSt()) { + _content = base::make_unique_q(this); + parent->sizeValue() | rpl::start_with_next([this](const QSize &size) { + _content->resizeToWidth(size.width()); + resize(size.width(), _content->height()); + }, lifetime()); +} + +crl::time PeerListWidgets::paintRow( + Painter &p, + crl::time now, + bool selected, + not_null row) { + const auto &st = row->computeSt(_st.item); + + row->lazyInitialize(st); + const auto outerWidth = _content->width(); + const auto w = outerWidth; + + auto refreshStatusAt = row->refreshStatusTime(); + if (refreshStatusAt > 0 && now >= refreshStatusAt) { + row->refreshStatus(); + refreshStatusAt = row->refreshStatusTime(); + } + const auto refreshStatusIn = (refreshStatusAt > 0) + ? std::max(refreshStatusAt - now, crl::time(1)) + : 0; + + row->paintUserpic( + p, + st, + st.photoPosition.x(), + st.photoPosition.y(), + outerWidth); + + p.setPen(st::contactsNameFg); + + const auto skipRight = st.photoPosition.x(); + const auto rightActionSize = row->rightActionSize(); + const auto rightActionMargins = rightActionSize.isEmpty() + ? QMargins() + : row->rightActionMargins(); + const auto &name = row->name(); + const auto namePosition = st.namePosition; + const auto namex = namePosition.x(); + const auto namey = namePosition.y(); + auto namew = outerWidth - namex - skipRight; + if (!rightActionSize.isEmpty() + && (namey < rightActionMargins.top() + rightActionSize.height()) + && (namey + st.nameStyle.font->height + > rightActionMargins.top())) { + namew -= rightActionMargins.left() + + rightActionSize.width() + + rightActionMargins.right() + - skipRight; + } + const auto statusx = st.statusPosition.x(); + const auto statusy = st.statusPosition.y(); + auto statusw = outerWidth - statusx - skipRight; + if (!rightActionSize.isEmpty() + && (statusy < rightActionMargins.top() + rightActionSize.height()) + && (statusy + st::contactsStatusFont->height + > rightActionMargins.top())) { + statusw -= rightActionMargins.left() + + rightActionSize.width() + + rightActionMargins.right() + - skipRight; + } + namew -= row->paintNameIconGetWidth( + p, + [=] { updateRow(row); }, + now, + namex, + namey, + name.maxWidth(), + namew, + w, + selected); + auto nameCheckedRatio = row->disabled() ? 0. : row->checkedRatio(); + p.setPen(anim::pen(st.nameFg, st.nameFgChecked, nameCheckedRatio)); + name.drawLeftElided(p, namex, namey, namew, w); + + p.setFont(st::contactsStatusFont); + row->paintStatusText(p, st, statusx, statusy, statusw, w, selected); + + row->elementsPaint(p, outerWidth, selected, 0); + + return refreshStatusIn; +} + +void PeerListWidgets::appendRow(std::unique_ptr row) { + Expects(row != nullptr); + + if (_rowsById.find(row->id()) == _rowsById.cend()) { + const auto raw = row.get(); + const auto &st = raw->computeSt(_st.item); + raw->setAbsoluteIndex(_rows.size()); + _rows.push_back(std::move(row)); + + const auto widget = _content->add( + object_ptr::fromRaw( + Ui::CreateSimpleSettingsButton( + _content.get(), + st.button.ripple, + st.button.textBgOver))); + widget->resize(widget->width(), st.height); + widget->paintRequest() | rpl::start_with_next([=, this] { + auto p = Painter(widget); + const auto selected = widget->isOver() || widget->isDown(); + paintRow(p, crl::now(), selected, raw); + }, widget->lifetime()); + + widget->setClickedCallback([this, raw] { + _controller->rowClicked(raw); + }); + } +} + +PeerListRow *PeerListWidgets::findRow(PeerListRowId id) { + const auto it = _rowsById.find(id); + return (it == _rowsById.cend()) ? nullptr : it->second.get(); +} + +void PeerListWidgets::updateRow(not_null row) { + const auto it = ranges::find_if( + _rows, + [row](const auto &r) { return r.get() == row; }); + if (it != _rows.end()) { + const auto index = std::distance(_rows.begin(), it); + if (const auto widget = _content->widgetAt(index)) { + widget->update(); + } + } +} + +int PeerListWidgets::fullRowsCount() { + return _rows.size(); +} + +[[nodiscard]] not_null PeerListWidgets::rowAt(int index) { + Expects(index >= 0 && index < _rows.size()); + + return _rows[index].get(); +} + +void PeerListWidgets::refreshRows() { + _content->resizeToWidth(width()); + resize(width(), _content->height()); +} + +void PeerListWidgetsDelegate::setContent(PeerListWidgets *content) { + _content = content; +} + +void PeerListWidgetsDelegate::setUiShow( + std::shared_ptr uiShow) { + _uiShow = std::move(uiShow); +} + +void PeerListWidgetsDelegate::peerListSetHideEmpty(bool hide) { + Unexpected("...PeerListWidgetsDelegate::peerListSetHideEmpty"); +} + +void PeerListWidgetsDelegate::peerListAppendRow( + std::unique_ptr row) { + _content->appendRow(std::move(row)); +} + +void PeerListWidgetsDelegate::peerListAppendSearchRow( + std::unique_ptr row) { + Unexpected("...PeerListWidgetsDelegate::peerListAppendSearchRow"); +} + +void PeerListWidgetsDelegate::peerListAppendFoundRow( + not_null row) { + Unexpected("...PeerListWidgetsDelegate::peerListAppendFoundRow"); +} + +void PeerListWidgetsDelegate::peerListPrependRow( + std::unique_ptr row) { + Unexpected("...PeerListWidgetsDelegate::peerListPrependRow"); +} + +void PeerListWidgetsDelegate::peerListPrependRowFromSearchResult( + not_null row) { + Unexpected( + "...PeerListWidgetsDelegate::peerListPrependRowFromSearchResult"); +} + +PeerListRow* PeerListWidgetsDelegate::peerListFindRow(PeerListRowId id) { + return _content->findRow(id); +} + +auto PeerListWidgetsDelegate::peerListLastRowMousePosition() +-> std::optional { + Unexpected("...PeerListWidgetsDelegate::peerListLastRowMousePosition"); +} + +void PeerListWidgetsDelegate::peerListUpdateRow(not_null row) { + _content->updateRow(row); +} + +void PeerListWidgetsDelegate::peerListRemoveRow(not_null row) { + Unexpected("...PeerListWidgetsDelegate::peerListRemoveRow"); +} + +void PeerListWidgetsDelegate::peerListConvertRowToSearchResult( + not_null row) { + Unexpected( + "...PeerListWidgetsDelegate::peerListConvertRowToSearchResult"); +} + +void PeerListWidgetsDelegate::peerListSetRowChecked( + not_null row, + bool checked) { + Unexpected("...PeerListWidgetsDelegate::peerListSetRowChecked"); +} + +void PeerListWidgetsDelegate::peerListSetRowHidden( + not_null row, + bool hidden) { + Unexpected("...PeerListWidgetsDelegate::peerListSetRowHidden"); +} + +void PeerListWidgetsDelegate::peerListSetForeignRowChecked( + not_null row, + bool checked, + anim::type animated) { +} + +int PeerListWidgetsDelegate::peerListFullRowsCount() { + return _content->fullRowsCount(); +} + +not_null PeerListWidgetsDelegate::peerListRowAt(int index) { + return _content->rowAt(index); +} + +int PeerListWidgetsDelegate::peerListSearchRowsCount() { + Unexpected("...PeerListWidgetsDelegate::peerListSearchRowsCount"); +} + +not_null PeerListWidgetsDelegate::peerListSearchRowAt(int) { + Unexpected("...PeerListWidgetsDelegate::peerListSearchRowAt"); +} + +void PeerListWidgetsDelegate::peerListRefreshRows() { + _content->refreshRows(); +} + +void PeerListWidgetsDelegate::peerListSetDescription( + object_ptr) { + Unexpected("...PeerListWidgetsDelegate::peerListSetDescription"); +} + +void PeerListWidgetsDelegate::peerListSetSearchNoResults( + object_ptr) { + Unexpected("...PeerListWidgetsDelegate::peerListSetSearchNoResults"); +} + +void PeerListWidgetsDelegate::peerListSetAboveWidget( + object_ptr) { + Unexpected("...PeerListWidgetsDelegate::peerListSetAboveWidget"); +} + +void PeerListWidgetsDelegate::peerListSetAboveSearchWidget( + object_ptr) { + Unexpected("...PeerListWidgetsDelegate::peerListSetAboveSearchWidget"); +} + +void PeerListWidgetsDelegate::peerListSetBelowWidget( + object_ptr) { + Unexpected("...PeerListWidgetsDelegate::peerListSetBelowWidget"); +} + +void PeerListWidgetsDelegate::peerListSetSearchMode(PeerListSearchMode mode) { + Unexpected("...PeerListWidgetsDelegate::peerListSetSearchMode"); +} + +void PeerListWidgetsDelegate::peerListMouseLeftGeometry() { + Unexpected("...PeerListWidgetsDelegate::peerListMouseLeftGeometry"); +} + +void PeerListWidgetsDelegate::peerListSortRows( + Fn) { + Unexpected("...PeerListWidgetsDelegate::peerListSortRows"); +} + +int PeerListWidgetsDelegate::peerListPartitionRows( + Fn border) { + Unexpected("...PeerListWidgetsDelegate::peerListPartitionRows"); +} + +State PeerListWidgetsDelegate::peerListSaveState() const { + Unexpected("...PeerListWidgetsDelegate::peerListSaveState"); + return nullptr; +} + +void PeerListWidgetsDelegate::peerListRestoreState( + std::unique_ptr state) { + Unexpected("...PeerListWidgetsDelegate::peerListRestoreState"); +} + +void PeerListWidgetsDelegate::peerListShowRowMenu( + not_null row, + bool highlightRow, + Fn)> destroyed) { +} + +void PeerListWidgetsDelegate::peerListSelectSkip(int direction) { + // _content->selectSkip(direction); +} + +void PeerListWidgetsDelegate::peerListPressLeftToContextMenu(bool shown) { + Unexpected("...PeerListWidgetsDelegate::peerListPressLeftToContextMenu"); +} + +bool PeerListWidgetsDelegate::peerListTrackRowPressFromGlobal(QPoint) { + return false; +} + +std::shared_ptr PeerListWidgetsDelegate::peerListUiShow() { + Expects(_uiShow != nullptr); + return _uiShow; +} + +void PeerListWidgetsDelegate::peerListAddSelectedPeerInBunch( + not_null peer) { + Unexpected("...PeerListWidgetsDelegate::peerListAddSelectedPeerInBunch"); +} + +void PeerListWidgetsDelegate::peerListAddSelectedRowInBunch( + not_null row) { + Unexpected("...PeerListWidgetsDelegate::peerListAddSelectedRowInBunch"); +} + +void PeerListWidgetsDelegate::peerListFinishSelectedRowsBunch() { + Unexpected("...PeerListWidgetsDelegate::peerListFinishSelectedRowsBunch"); +} + +void PeerListWidgetsDelegate::peerListSetTitle(rpl::producer title) { + Unexpected("...PeerListWidgetsDelegate::peerListSetTitle"); +} + +void PeerListWidgetsDelegate::peerListSetAdditionalTitle( + rpl::producer title) { + Unexpected("...PeerListWidgetsDelegate::peerListSetAdditionalTitle"); +} + +bool PeerListWidgetsDelegate::peerListIsRowChecked( + not_null row) { + Unexpected("...PeerListWidgetsDelegate::peerListIsRowChecked"); + return false; +} + +void PeerListWidgetsDelegate::peerListScrollToTop() { + Unexpected("...PeerListWidgetsDelegate::peerListScrollToTop"); +} + +int PeerListWidgetsDelegate::peerListSelectedRowsCount() { + Unexpected("...PeerListWidgetsDelegate::peerListSelectedRowsCount"); + return 0; +} diff --git a/Telegram/SourceFiles/boxes/peer_list_widgets.h b/Telegram/SourceFiles/boxes/peer_list_widgets.h new file mode 100644 index 000000000..d139487bc --- /dev/null +++ b/Telegram/SourceFiles/boxes/peer_list_widgets.h @@ -0,0 +1,110 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "boxes/peer_list_box.h" + +namespace Ui { +class VerticalLayout; +} // namespace Ui + +class PeerListWidgets : public Ui::RpWidget { +public: + PeerListWidgets( + not_null parent, + not_null controller); + + crl::time paintRow( + Painter &p, + crl::time now, + bool selected, + not_null row); + void appendRow(std::unique_ptr row); + PeerListRow* findRow(PeerListRowId id); + void updateRow(not_null row); + int fullRowsCount(); + [[nodiscard]] not_null rowAt(int index); + void refreshRows(); + +private: + const not_null _controller; + const style::PeerList &_st; + base::unique_qptr _content; + + std::vector> _rows; + std::map> _rowsById; + std::map>> _rowsByPeer; +}; + +class PeerListWidgetsDelegate : public PeerListDelegate { +public: + void setContent(PeerListWidgets *content); + void setUiShow(std::shared_ptr uiShow); + + void peerListSetHideEmpty(bool hide) override; + void peerListAppendRow(std::unique_ptr row) override; + void peerListAppendSearchRow(std::unique_ptr row) override; + void peerListAppendFoundRow(not_null row) override; + void peerListPrependRow(std::unique_ptr row) override; + void peerListPrependRowFromSearchResult( + not_null row) override; + PeerListRow *peerListFindRow(PeerListRowId id) override; + std::optional peerListLastRowMousePosition() override; + void peerListUpdateRow(not_null row) override; + void peerListRemoveRow(not_null row) override; + void peerListConvertRowToSearchResult( + not_null row) override; + void peerListSetRowChecked( + not_null row, + bool checked) override; + void peerListSetRowHidden( + not_null row, + bool hidden) override; + void peerListSetForeignRowChecked( + not_null row, + bool checked, + anim::type animated) override; + int peerListFullRowsCount() override; + not_null peerListRowAt(int index) override; + int peerListSearchRowsCount() override; + not_null peerListSearchRowAt(int index) override; + void peerListRefreshRows() override; + void peerListSetDescription(object_ptr) override; + void peerListSetSearchNoResults(object_ptr) override; + void peerListSetAboveWidget(object_ptr) override; + void peerListSetAboveSearchWidget(object_ptr) override; + void peerListSetBelowWidget(object_ptr) override; + void peerListSetSearchMode(PeerListSearchMode mode) override; + void peerListMouseLeftGeometry() override; + void peerListSortRows( + Fn) override; + int peerListPartitionRows(Fn border) override; + std::unique_ptr peerListSaveState() const override; + void peerListRestoreState(std::unique_ptr state) override; + void peerListShowRowMenu( + not_null row, + bool highlightRow, + Fn)> destroyed = nullptr) override; + void peerListSelectSkip(int direction) override; + void peerListPressLeftToContextMenu(bool shown) override; + bool peerListTrackRowPressFromGlobal(QPoint globalPosition) override; + std::shared_ptr peerListUiShow() override; + void peerListAddSelectedPeerInBunch(not_null peer) override; + void peerListAddSelectedRowInBunch(not_null row) override; + void peerListFinishSelectedRowsBunch() override; + void peerListSetTitle(rpl::producer title) override; + void peerListSetAdditionalTitle(rpl::producer title) override; + bool peerListIsRowChecked(not_null row) override; + void peerListScrollToTop() override; + int peerListSelectedRowsCount() override; + +private: + PeerListWidgets *_content = nullptr; + std::shared_ptr _uiShow; + +}; diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index 3ac13da20..e42151d75 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -26,17 +26,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_credits_graphics.h" #include "ui/boxes/confirm_box.h" #include "ui/controls/userpic_button.h" +#include "ui/effects/credits_graphics.h" #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg. #include "ui/image/image_prepare.h" #include "ui/layers/generic_box.h" +#include "ui/painter.h" #include "ui/rect.h" #include "ui/text/text_utilities.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/peer_bubble.h" #include "styles/style_boxes.h" +#include "styles/style_chat.h" #include "styles/style_credits.h" #include "styles/style_giveaway.h" +#include "styles/style_info.h" // inviteLinkSubscribeBoxTerms #include "styles/style_layers.h" #include "styles/style_premium.h" #include "styles/style_settings.h" @@ -92,6 +97,44 @@ struct PaidMediaData { }; } +void AddTerms( + not_null box, + not_null button, + const style::Box &stBox) { + const auto terms = Ui::CreateChild( + button->parentWidget(), + tr::lng_channel_invite_subscription_terms( + lt_link, + rpl::combine( + tr::lng_paid_react_agree_link(), + tr::lng_group_invite_subscription_about_url() + ) | rpl::map([](const QString &text, const QString &url) { + return Ui::Text::Link(text, url); + }), + Ui::Text::RichLangValue), + st::inviteLinkSubscribeBoxTerms); + const auto &buttonPadding = stBox.buttonPadding; + const auto style = box->lifetime().make_state(style::Box{ + .buttonPadding = buttonPadding + QMargins(0, 0, 0, terms->height()), + .buttonHeight = stBox.buttonHeight, + .button = stBox.button, + .margin = stBox.margin, + .title = stBox.title, + .bg = stBox.bg, + .titleAdditionalFg = stBox.titleAdditionalFg, + .shadowIgnoreTopSkip = stBox.shadowIgnoreTopSkip, + .shadowIgnoreBottomSkip = stBox.shadowIgnoreBottomSkip, + }); + button->geometryValue() | rpl::start_with_next([=](const QRect &rect) { + terms->resizeToWidth(box->width() + - rect::m::sum::h(st::boxRowPadding)); + terms->moveToLeft( + rect.x() + (rect.width() - terms->width()) / 2, + rect::bottom(rect) + buttonPadding.bottom() / 2); + }, terms->lifetime()); + box->setStyle(*style); +} + [[nodiscard]] rpl::producer SendCreditsConfirmText( not_null session, not_null form) { @@ -150,6 +193,18 @@ struct PaidMediaData { } const auto bot = session->data().user(form->botId); + if (form->invoice.subscriptionPeriod) { + return (bot->botInfo + ? tr::lng_credits_box_out_subscription_bot + : tr::lng_credits_box_out_subscription_business)( + lt_count, + rpl::single(form->invoice.amount) | tr::to_count(), + lt_title, + rpl::single(TextWithEntities{ form->title }), + lt_recipient, + rpl::single(TextWithEntities{ bot->name() }), + Ui::Text::RichLangValue); + } return tr::lng_credits_box_out_sure( lt_count, rpl::single(form->invoice.amount) | tr::to_count(), @@ -190,6 +245,57 @@ struct PaidMediaData { st::defaultUserpicButton); } +[[nodiscard]] not_null SendCreditsBadge( + not_null parent, + int credits) { + const auto widget = Ui::CreateChild(parent); + const auto &font = st::chatGiveawayBadgeFont; + const auto text = QString::number(credits); + const auto iconHeight = font->ascent - font->descent; + const auto iconWidth = iconHeight + st::lineWidth; + const auto width = font->width(text) + iconWidth + st::lineWidth; + const auto inner = QRect(0, 0, width, font->height); + const auto rect = inner + st::subscriptionCreditsBadgePadding; + const auto size = rect.size(); + const auto svg = widget->lifetime().make_state( + Ui::Premium::Svg()); + const auto half = st::chatGiveawayBadgeStroke / 2.; + const auto left = st::subscriptionCreditsBadgePadding.left(); + const auto smaller = QRectF(rect.translated(-rect.topLeft())) + - Margins(half); + const auto radius = smaller.height() / 2.; + widget->resize(size); + + widget->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(widget); + auto hq = PainterHighQualityEnabler(p); + p.setPen(QPen(st::premiumButtonFg, st::chatGiveawayBadgeStroke * 1.)); + p.setBrush(st::creditsBg3); + p.drawRoundedRect(smaller, radius, radius); + + p.translate(0, font->descent / 2); + + p.setPen(st::premiumButtonFg); + p.setBrush(st::premiumButtonFg); + svg->render( + &p, + QRect( + left, + half + (inner.height() - iconHeight) / 2, + iconHeight, + iconHeight)); + + p.setFont(font); + p.drawText( + left + iconWidth, + st::subscriptionCreditsBadgePadding.top() + font->ascent, + text); + + }, widget->lifetime()); + + return widget; +} + } // namespace void SendCreditsBox( @@ -203,7 +309,8 @@ void SendCreditsBox( rpl::variable confirmButtonBusy = false; }; const auto state = box->lifetime().make_state(); - box->setStyle(st::giveawayGiftCodeBox); + const auto &stBox = st::giveawayGiftCodeBox; + box->setStyle(stBox); box->setNoContentMargin(true); const auto session = form->invoice.session; @@ -245,14 +352,36 @@ void SendCreditsBox( content, SendCreditsThumbnail(content, session, form.get(), photoSize))); thumb->setAttribute(Qt::WA_TransparentForMouseEvents); + if (form->invoice.subscriptionPeriod) { + const auto badge = SendCreditsBadge(content, form->invoice.amount); + thumb->geometryValue() | rpl::start_with_next([=](const QRect &r) { + badge->moveToLeft( + r.x() + (r.width() - badge->width()) / 2, + rect::bottom(r) - badge->height() / 2); + }, badge->lifetime()); + Ui::AddSkip(content); + Ui::AddSkip(content); + } Ui::AddSkip(content); box->addRow(object_ptr>( box, object_ptr( box, - tr::lng_credits_box_out_title(), + form->invoice.subscriptionPeriod + ? rpl::single(form->title) + : tr::lng_credits_box_out_title(), st::settingsPremiumUserTitle))); + if (form->invoice.subscriptionPeriod && form->botId && form->photo) { + Ui::AddSkip(content); + Ui::AddSkip(content); + const auto bot = session->data().user(form->botId); + box->addRow( + object_ptr>( + box, + Ui::CreatePeerBubble(box, bot))); + Ui::AddSkip(content); + } Ui::AddSkip(content); box->addRow(object_ptr>( box, @@ -306,6 +435,9 @@ void SendCreditsBox( } }).send(); }); + if (form->invoice.subscriptionPeriod) { + AddTerms(box, button, stBox); + } { using namespace Info::Statistics; const auto loadingAnimation = InfiniteRadialAnimationWidget( @@ -317,12 +449,14 @@ void SendCreditsBox( SetButtonMarkedLabel( button, rpl::combine( - tr::lng_credits_box_out_confirm( - lt_count, - rpl::single(form->invoice.amount) | tr::to_count(), - lt_emoji, - rpl::single(CreditsEmojiSmall(session)), - Ui::Text::RichLangValue), + (form->invoice.subscriptionPeriod + ? tr::lng_credits_box_out_subscription_confirm + : tr::lng_credits_box_out_confirm)( + lt_count, + rpl::single(form->invoice.amount) | tr::to_count(), + lt_emoji, + rpl::single(CreditsEmojiSmall(session)), + Ui::Text::RichLangValue), state->confirmButtonBusy.value() ) | rpl::map([](TextWithEntities &&text, bool busy) { return busy ? TextWithEntities() : std::move(text); @@ -332,7 +466,7 @@ void SendCreditsBox( box->getDelegate()->style().button.textFg->c); const auto buttonWidth = st::boxWidth - - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding); + - rect::m::sum::h(stBox.buttonPadding); button->widthValue() | rpl::filter([=] { return (button->widthNoMargins() != buttonWidth); }) | rpl::start_with_next([=] { @@ -341,15 +475,11 @@ void SendCreditsBox( { const auto close = Ui::CreateChild( - box.get(), + content, st::boxTitleClose); - close->setClickedCallback([=] { - box->closeBox(); - }); - box->widthValue( - ) | rpl::start_with_next([=](int width) { + close->setClickedCallback([=] { box->closeBox(); }); + content->widthValue() | rpl::start_with_next([=](int) { close->moveToRight(0, 0); - close->raise(); }, close->lifetime()); } diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 118532d16..7d5793727 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -669,7 +669,8 @@ bool Instance::inCall() const { return false; } const auto state = _currentCall->state(); - return (state != Call::State::Busy); + return (state != Call::State::Busy) + && (state != Call::State::WaitingUserConfirmation); } bool Instance::inGroupCall() const { diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 6f0ab0f6a..74ddcb8f2 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -993,7 +993,12 @@ void Panel::paint(QRect clip) { bool Panel::handleClose() const { if (_call) { - window()->hide(); + if (_call->state() == Call::State::WaitingUserConfirmation + || _call->state() == Call::State::Busy) { + _call->hangup(); + } else { + window()->hide(); + } return true; } return false; @@ -1028,6 +1033,7 @@ void Panel::stateChanged(State state) { _startVideo = base::make_unique_q( widget(), st::callStartVideo); + _startVideo->show(); _startVideo->setText(tr::lng_call_start_video()); _startVideo->clicks() | rpl::map_to(true) | rpl::start_to_stream( _startOutgoingRequests, diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index eb693524e..2ddd76836 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs; constexpr auto AppNameOld = "AyuGram for Windows"_cs; constexpr auto AppName = "AyuGram Desktop"_cs; constexpr auto AppFile = "AyuGram"_cs; -constexpr auto AppVersion = 5008002; -constexpr auto AppVersionStr = "5.8.2"; +constexpr auto AppVersion = 5008003; +constexpr auto AppVersionStr = "5.8.3"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 95ced4eb8..cd1ea9c65 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1980,6 +1980,19 @@ rpl::producer<> Session::pinnedDialogsOrderUpdated() const { return _pinnedDialogsOrderUpdated.events(); } +Session::CreditsSubsRebuilderPtr Session::createCreditsSubsRebuilder() { + if (auto result = activeCreditsSubsRebuilder()) { + return result; + } + auto result = std::make_shared(); + _creditsSubsRebuilder = result; + return result; +} + +Session::CreditsSubsRebuilderPtr Session::activeCreditsSubsRebuilder() const { + return _creditsSubsRebuilder.lock(); +} + void Session::registerHeavyViewPart(not_null view) { _heavyViewParts.emplace(view); } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index b9d048062..84e735c09 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -70,6 +70,7 @@ class Chatbots; class BusinessInfo; struct ReactionId; struct UnavailableReason; +struct CreditsStatusSlice; struct RepliesReadTillUpdate { FullMsgId id; @@ -337,6 +338,11 @@ public: void notifyPinnedDialogsOrderUpdated(); [[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const; + using CreditsSubsRebuilder = rpl::event_stream; + using CreditsSubsRebuilderPtr = std::shared_ptr; + [[nodiscard]] CreditsSubsRebuilderPtr createCreditsSubsRebuilder(); + [[nodiscard]] CreditsSubsRebuilderPtr activeCreditsSubsRebuilder() const; + void registerRestricted( not_null item, const QString &reason); @@ -1095,6 +1101,8 @@ private: MessageIdsList _mimeForwardIds; + std::weak_ptr _creditsSubsRebuilder; + using CredentialsWithGeneration = std::pair< const Passport::SavedCredentials, int>; diff --git a/Telegram/SourceFiles/data/data_subscriptions.h b/Telegram/SourceFiles/data/data_subscriptions.h index 69af0d4d6..c0d4de5a2 100644 --- a/Telegram/SourceFiles/data/data_subscriptions.h +++ b/Telegram/SourceFiles/data/data_subscriptions.h @@ -18,6 +18,7 @@ struct PeerSubscription final { } }; +using PhotoId = uint64; struct SubscriptionEntry final { explicit operator bool() const { return !id.isEmpty(); @@ -25,10 +26,14 @@ struct SubscriptionEntry final { QString id; QString inviteHash; + QString title; + QString slug; QDateTime until; PeerSubscription subscription; uint64 barePeerId = 0; + PhotoId photoId = PhotoId(0); bool cancelled = false; + bool cancelledByBot = false; bool expired = false; bool canRefulfill = false; }; diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h index 56e84f48c..3a19eec68 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.h +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h @@ -295,7 +295,7 @@ auto Entry::unreadStateChangeNotifier(bool required) { _flags |= Flag::InUnreadChangeBlock; const auto notify = required && inChatList(); const auto wasState = notify ? chatListUnreadState() : UnreadState(); - return gsl::finally([=] { + return gsl::finally([=, this] { _flags &= ~Flag::InUnreadChangeBlock; if (notify) { Assert(inChatList()); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 5a1d7e731..edb19fe09 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -130,19 +130,6 @@ int BinarySearchBlocksOrItems(const T &list, int edge) { return start; } -[[nodiscard]] bool CanSendReply(not_null item) { - if (item->isDeleted()) { - return false; - } - - const auto peer = item->history()->peer; - const auto topic = item->topic(); - return topic - ? Data::CanSendAnything(topic) - : (Data::CanSendAnything(peer) - && (!peer->isChannel() || peer->asChannel()->amIn())); -} - } // namespace // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html @@ -631,21 +618,13 @@ void HistoryInner::setupSwipeReply() { const auto replyToItemId = (selected.item ? selected.item : still)->fullId(); - if (canSendReply) { - _widget->replyToMessage({ - .messageId = replyToItemId, - .quote = selected.text, - .quoteOffset = selected.offset, - }); - if (!selected.text.empty()) { - _widget->clearSelected(); - } - } else { - HistoryView::Controls::ShowReplyToChatBox(show, { - .messageId = replyToItemId, - .quote = selected.text, - .quoteOffset = selected.offset, - }); + _widget->replyToMessage({ + .messageId = replyToItemId, + .quote = selected.text, + .quoteOffset = selected.offset, + }); + if (!selected.text.empty()) { + _widget->clearSelected(); } }; return false; @@ -2644,26 +2623,13 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto quoteOffset = selected.offset; text.replace('&', u"&&"_q); _menu->addAction(text, [=] { - const auto still = session->data().message(itemId); - const auto forceAnotherChat = base::IsCtrlPressed() - && still - && still->allowsForward(); - if (canSendReply && !forceAnotherChat) { - _widget->replyToMessage({ - .messageId = itemId, - .quote = quote, - .quoteOffset = quoteOffset, - }); - if (!quote.empty()) { - _widget->clearSelected(); - } - } else { - const auto show = controller->uiShow(); - HistoryView::Controls::ShowReplyToChatBox(show, { - .messageId = itemId, - .quote = quote, - .quoteOffset = quoteOffset, - }); + _widget->replyToMessage({ + .messageId = itemId, + .quote = quote, + .quoteOffset = quoteOffset, + }); + if (!quote.empty()) { + _widget->clearSelected(); } }, &st::menuIconReply); } @@ -4847,3 +4813,16 @@ auto HistoryInner::DelegateMixin() -> std::unique_ptr { return std::make_unique(); } + +bool CanSendReply(not_null item) { + if (item->isDeleted()) { + return false; + } + + const auto peer = item->history()->peer; + const auto topic = item->topic(); + return topic + ? Data::CanSendAnything(topic) + : (Data::CanSendAnything(peer) + && (!peer->isChannel() || peer->asChannel()->amIn())); +} diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 818b5435b..4f0ba7bfc 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -555,3 +555,5 @@ private: bool _wasForceClickPreview = false; }; + +[[nodiscard]] bool CanSendReply(not_null item); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 833e66e8b..332f2cc08 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -4701,6 +4701,21 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return preparePaymentSentText(); }; + auto preparePaymentSentMe = [&](const MTPDmessageActionPaymentSentMe &data) { + auto result = PreparedServiceText(); + result.text = (data.is_recurring_used() + ? tr::lng_action_payment_bot_recurring + : tr::lng_action_payment_bot_done)( + tr::now, + lt_amount, + AmountAndStarCurrency( + &_history->session(), + data.vtotal_amount().v, + qs(data.vcurrency())), + Ui::Text::WithEntities); + return result; + }; + auto prepareScreenshotTaken = [this](const MTPDmessageActionScreenshotTaken &) { auto result = PreparedServiceText(); if (out()) { @@ -5504,7 +5519,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { prepareSecureValuesSent, prepareContactSignUp, prepareProximityReached, - PrepareErrorText, + preparePaymentSentMe, PrepareErrorText, prepareGroupCall, prepareInviteToGroupCall, diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 8bcfc7570..4031c19f3 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -2028,24 +2028,12 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived( UserData *samePeerBot, MsgId samePeerReplyTo) { if (samePeerBot) { - if (_history) { - const auto textWithTags = TextWithTags{ - '@' + samePeerBot->username() + ' ' + query, - TextWithTags::Tags(), - }; - MessageCursor cursor = { - int(textWithTags.text.size()), - int(textWithTags.text.size()), - Ui::kQFixedMax, - }; - _history->setLocalDraft(std::make_unique( - textWithTags, - FullReplyTo(), - cursor, - Data::WebPageDraft())); - applyDraft(); - return true; + const auto to = controller()->currentDialogsEntryState(); + if (!to.key.owningHistory()) { + return false; } + controller()->switchInlineQuery(to, samePeerBot, query); + return true; } else if (const auto bot = _peer ? _peer->asUser() : nullptr) { const auto to = bot->isBot() ? bot->botInfo->inlineReturnTo @@ -2273,7 +2261,7 @@ void HistoryWidget::showHistory( _showAtMsgHighlightPart = {}; _showAtMsgHighlightPartOffsetHint = 0; - const auto wasDialogsEntryState = computeDialogsEntryState(); + const auto wasState = controller()->currentDialogsEntryState(); const auto startBot = (showAtMsgId == ShowAndStartBotMsgId); if (startBot) { showAtMsgId = ShowAtTheEndMsgId; @@ -2378,8 +2366,8 @@ void HistoryWidget::showHistory( if (const auto user = _peer->asUser()) { if (const auto &info = user->botInfo) { if (startBot) { - if (wasDialogsEntryState.key) { - info->inlineReturnTo = wasDialogsEntryState; + if (wasState.key) { + info->inlineReturnTo = wasState; } sendBotStartCommand(); _history->clearLocalDraft({}); @@ -2614,8 +2602,8 @@ void HistoryWidget::showHistory( if (const auto user = _peer->asUser()) { if (const auto &info = user->botInfo) { if (startBot) { - if (wasDialogsEntryState.key) { - info->inlineReturnTo = wasDialogsEntryState; + if (wasState.key) { + info->inlineReturnTo = wasState; } sendBotStartCommand(); } @@ -8060,7 +8048,15 @@ void HistoryWidget::clearFieldText( void HistoryWidget::replyToMessage(FullReplyTo id) { if (const auto item = session().data().message(id.messageId)) { - replyToMessage(item, id.quote, id.quoteOffset); + if (CanSendReply(item) && !base::IsCtrlPressed()) { + replyToMessage(item, id.quote, id.quoteOffset); + } else if (item->allowsForward()) { + const auto show = controller()->uiShow(); + HistoryView::Controls::ShowReplyToChatBox(show, id); + } else { + controller()->showToast( + tr::lng_error_cant_reply_other(tr::now)); + } } } diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 95c48c0e2..92e02a3c7 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -1887,7 +1887,7 @@ not_null ListWidget::elementPathShiftGradient() { } void ListWidget::elementReplyTo(const FullReplyTo &to) { - replyToMessageRequestNotify(to); + replyToMessageRequestNotify(to, base::IsCtrlPressed()); } void ListWidget::elementStartInteraction(not_null view) { diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 1e18a5cd8..4b065e664 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -1659,7 +1659,8 @@ void Message::draw(Painter &p, const PaintContext &context) const { (right - (st::msgSelectionOffset * progress - st.size) / 2 - st::msgPadding.right() / 2 - - st.size), + - st.size + - st::historyScroll.deltax), rect::bottom(g) - st.size - st::msgSelectionBottomSkip); { p.setPen(QPen(st.border, st.width)); diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp index e75a360c7..41de275e9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp @@ -255,7 +255,6 @@ DocumentData *Sticker::document() { void Sticker::stickerClearLoopPlayed() { _oncePlayed = false; - _premiumEffectPlayed = false; _premiumEffectSkipped = false; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index 515ab33e1..a9bc9687b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -634,10 +634,13 @@ QSize WebPage::countOptimalSize() { _durationWidth = st::msgDateFont->width(_duration); } if (!_openButton.isEmpty()) { - accumulate_max( - maxWidth, - rect::m::sum::h(st::historyPageButtonPadding) - + _openButton.maxWidth()); + const auto w = rect::m::sum::h(st::historyPageButtonPadding) + + _openButton.maxWidth(); + if (sponsored) { + accumulate_max(maxWidth, w); + } else { + maxWidth += w; + } } maxWidth += rect::m::sum::h(padding); minHeight += rect::m::sum::v(padding); diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp index b78da5c17..1fbf97797 100644 --- a/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp @@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/wrap/slide_wrap.h" #include "ui/ui_utility.h" +#include "styles/style_color_indices.h" #include "styles/style_credits.h" #include "styles/style_giveaway.h" #include "styles/style_info.h" @@ -59,7 +60,6 @@ namespace { constexpr auto kDoneTooltipDuration = 5 * crl::time(1000); constexpr auto kAdditionalPrizeLengthMax = 128; -constexpr auto kColorIndexCredits = int(1); [[nodiscard]] QDateTime ThreeDaysAfterToday() { auto dateNow = QDateTime::currentDateTime(); @@ -370,7 +370,7 @@ void CreateGiveawayBox( prepaid->credits ? GiveawayType::PrepaidCredits : GiveawayType::Prepaid, - prepaid->credits ? kColorIndexCredits : prepaid->id, + prepaid->credits ? st::colorIndexOrange : prepaid->id, tr::lng_boosts_prepaid_giveaway_single(), prepaid->credits ? tr::lng_boosts_prepaid_giveaway_credits_status( @@ -508,7 +508,7 @@ void CreateGiveawayBox( object_ptr( box, GiveawayType::Credits, - kColorIndexCredits, + st::colorIndexOrange, tr::lng_credits_summary_title(), tr::lng_giveaway_create_subtitle(), std::move(badge))); diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway_type_row.cpp b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway_type_row.cpp index b97184dba..60c1814ab 100644 --- a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway_type_row.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway_type_row.cpp @@ -16,14 +16,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/checkbox.h" #include "styles/style_boxes.h" #include "styles/style_chat.h" +#include "styles/style_color_indices.h" #include "styles/style_giveaway.h" #include "styles/style_statistics.h" namespace Giveaway { -constexpr auto kColorIndexSpecific = int(4); -constexpr auto kColorIndexRandom = int(2); - GiveawayTypeRow::GiveawayTypeRow( not_null parent, Type type, @@ -32,7 +30,7 @@ GiveawayTypeRow::GiveawayTypeRow( : GiveawayTypeRow( parent, type, - (type == Type::SpecificUsers) ? kColorIndexSpecific : kColorIndexRandom, + (type == Type::SpecificUsers) ? st::colorIndexBlue : st::colorIndexGreen, (type == Type::SpecificUsers) ? tr::lng_giveaway_award_option() : (type == Type::Random) diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/boosts/info_boosts_inner_widget.cpp index e2436585c..5b6648220 100644 --- a/Telegram/SourceFiles/info/channel_statistics/boosts/info_boosts_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/boosts/info_boosts_inner_widget.cpp @@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/slider_natural_width.h" #include "ui/wrap/slide_wrap.h" #include "ui/ui_utility.h" +#include "styles/style_color_indices.h" #include "styles/style_dialogs.h" // dialogsSearchTabs #include "styles/style_giveaway.h" #include "styles/style_info.h" @@ -339,7 +340,6 @@ void InnerWidget::fill() { Ui::AddSkip(inner); if (!status.prepaidGiveaway.empty()) { - constexpr auto kColorIndexCredits = int(1); const auto multiplier = Api::PremiumGiftCodeOptions(_peer) .giveawayBoostsPerPremium(); Ui::AddSkip(inner); @@ -352,7 +352,7 @@ void InnerWidget::fill() { g.credits ? GiveawayTypeRow::Type::PrepaidCredits : GiveawayTypeRow::Type::Prepaid, - g.credits ? kColorIndexCredits : g.id, + g.credits ? st::colorIndexOrange : g.id, g.credits ? tr::lng_boosts_prepaid_giveaway_single() : tr::lng_boosts_prepaid_giveaway_quantity( diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp index 3f98b5f23..3a2250f39 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp @@ -52,6 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/vertical_list.h" #include "ui/widgets/fields/input_field.h" #include "ui/widgets/label_with_custom_emoji.h" +#include "ui/widgets/peer_bubble.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/slider_natural_width.h" #include "ui/wrap/slide_wrap.h" @@ -1199,58 +1200,10 @@ void InnerWidget::fill() { AddRecipient(box, recipient); } if (isIn) { - const auto peerBubble = box->addRow( + box->addRow( object_ptr>( box, - object_ptr(box)))->entity(); - peerBubble->setAttribute( - Qt::WA_TransparentForMouseEvents); - const auto left = Ui::CreateChild( - peerBubble, - peer, - st::uploadUserpicButton); - const auto right = Ui::CreateChild( - peerBubble, - Info::Profile::NameValue(peer), - st::channelEarnSemiboldLabel); - rpl::combine( - left->sizeValue(), - right->sizeValue() - ) | rpl::start_with_next([=]( - const QSize &leftSize, - const QSize &rightSize) { - const auto padding = QMargins( - st::chatGiveawayPeerPadding.left() * 2, - st::chatGiveawayPeerPadding.top(), - st::chatGiveawayPeerPadding.right(), - st::chatGiveawayPeerPadding.bottom()); - peerBubble->resize( - leftSize.width() - + rightSize.width() - + rect::m::sum::h(padding), - leftSize.height()); - left->moveToLeft(0, 0); - right->moveToRight( - padding.right(), - padding.top()); - const auto maxRightSize = box->width() - - rect::m::sum::h(st::boxRowPadding) - - rect::m::sum::h(padding) - - leftSize.width(); - if (rightSize.width() > maxRightSize) { - right->resizeToWidth(maxRightSize); - } - }, peerBubble->lifetime()); - peerBubble->paintRequest( - ) | rpl::start_with_next([=] { - auto p = QPainter(peerBubble); - auto hq = PainterHighQualityEnabler(p); - p.setPen(Qt::NoPen); - p.setBrush(st::windowBgOver); - const auto rect = peerBubble->rect(); - const auto radius = rect.height() / 2; - p.drawRoundedRect(rect, radius, radius); - }, peerBubble->lifetime()); + Ui::CreatePeerBubble(box, peer))); } const auto closeBox = [=] { box->closeBox(); }; { diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index abbd690c7..3dbe08b96 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_credits.h" #include "api/api_statistics.h" #include "boxes/peer_list_controllers.h" +#include "boxes/peer_list_widgets.h" #include "chat_helpers/stickers_gift_box_pack.h" #include "core/ui_integration.h" // Core::MarkedTextContext. #include "data/data_channel.h" @@ -24,6 +25,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "main/session/session_show.h" #include "settings/settings_credits_graphics.h" // PaintSubscriptionRightLabelCallback +#include "ui/dynamic_image.h" +#include "ui/dynamic_thumbnails.h" #include "ui/effects/credits_graphics.h" #include "ui/effects/outline_segments.h" // Ui::UnreadStoryOutlineGradient. #include "ui/effects/toggle_arrow.h" @@ -35,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "styles/style_boxes.h" +#include "styles/style_color_indices.h" #include "styles/style_credits.h" #include "styles/style_dialogs.h" // dialogsStoriesFull. #include "styles/style_layers.h" // boxRowPadding. @@ -47,9 +51,6 @@ namespace Info::Statistics { namespace { using BoostCallback = Fn; -constexpr auto kColorIndexCredits = int(1); -constexpr auto kColorIndexUnclaimed = int(3); -constexpr auto kColorIndexPending = int(4); [[nodiscard]] PeerListRowId UniqueRowIdFromEntry( const Data::CreditsHistoryEntry &entry) { @@ -70,6 +71,17 @@ void AddSubtitle( { 0, -subtitlePadding.top(), 0, -subtitlePadding.bottom() }); } +[[nodiscard]] object_ptr CreateShowMoreButton( + not_null parent, + rpl::producer title) { + auto owned = object_ptr( + parent, + std::move(title), + st::statisticsShowMoreButton); + Ui::AddToggleUpDownArrowToMoreButton(owned.data()); + return owned; +} + [[nodiscard]] QString FormatText( int value1, tr::phrase phrase1, int value2, tr::phrase phrase2, @@ -475,10 +487,10 @@ BoostRow::BoostRow(const Data::Boost &boost) , _boost(boost) , _userpic( Ui::EmptyUserpic::UserpicColor(boost.credits - ? kColorIndexCredits + ? st::colorIndexOrange : boost.isUnclaimed - ? kColorIndexUnclaimed - : kColorIndexPending), + ? st::colorIndexSea + : st::colorIndexBlue), QString()) { init(); } @@ -763,6 +775,18 @@ public: bool selected, bool actionSelected) override; + void paintStatusText( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int available, + int outer, + bool selected) override; + + const style::PeerListItem &computeSt( + const style::PeerListItem &st) const override; + private: void init(); @@ -776,8 +800,12 @@ private: QString _title; QString _name; + Ui::Text::String _description; Ui::Text::String _rightText; + std::shared_ptr _descriptionThumbnail; + QImage _descriptionThumbnailCache; + base::has_weak_ptr _guard; }; @@ -822,33 +850,37 @@ void CreditsRow::init() { const auto name = !isSpecial ? PeerListRow::generateName() : Ui::GenerateEntryName(_entry).text; - _name = (_entry.reaction || _entry.stargift || _entry.bareGiveawayMsgId) - ? Ui::GenerateEntryName(_entry).text - : _entry.title.isEmpty() + _name = _entry.title.isEmpty() + ? name + : (!_entry.subscriptionUntil.isNull() && !isSpecial) ? name : _entry.title; - const auto joiner = QString(QChar(' ')) + QChar(8212) + QChar(' '); setSkipPeerBadge(true); - PeerListRow::setCustomStatus( - langDateTime(_entry.date) - + (_entry.floodSkip - ? (joiner + tr::lng_credits_box_history_entry_floodskip_about( - tr::now, - lt_count_decimal, - _entry.floodSkip)) - : _entry.refunded - ? (joiner + tr::lng_channel_earn_history_return(tr::now)) - : _entry.pending - ? (joiner + tr::lng_channel_earn_history_pending(tr::now)) - : _entry.failed - ? (joiner + tr::lng_channel_earn_history_failed(tr::now)) - : !_entry.subscriptionUntil.isNull() - ? (joiner - + tr::lng_credits_box_history_entry_subscription(tr::now)) - : QString()) - + ((_entry.gift && isSpecial) - ? (joiner + tr::lng_credits_box_history_entry_anonymous(tr::now)) - : ((_name == name) ? QString() : (joiner + name)))); + const auto description = _entry.floodSkip + ? tr::lng_credits_box_history_entry_floodskip_about( + tr::now, + lt_count_decimal, + _entry.floodSkip) + : (!_entry.subscriptionUntil.isNull() && !_entry.title.isEmpty()) + ? _entry.title + : _entry.refunded + ? tr::lng_channel_earn_history_return(tr::now) + : _entry.pending + ? tr::lng_channel_earn_history_pending(tr::now) + : _entry.failed + ? tr::lng_channel_earn_history_failed(tr::now) + : !_entry.subscriptionUntil.isNull() + ? tr::lng_credits_box_history_entry_subscription(tr::now) + : (_entry.peerType + == Data::CreditsHistoryEntry::PeerType::PremiumBot) + ? tr::lng_credits_box_history_entry_via_premium_bot(tr::now) + : (_entry.gift && isSpecial) + ? tr::lng_credits_box_history_entry_anonymous(tr::now) + : (_name == name) + ? Ui::GenerateEntryName(_entry).text + : name; + _description.setText(st::defaultTextStyle, description); + PeerListRow::setCustomStatus(langDateTime(_entry.date)); if (_subscription) { PeerListRow::setCustomStatus((_subscription.expired ? tr::lng_credits_subscription_status_none @@ -858,6 +890,24 @@ void CreditsRow::init() { tr::now, lt_date, langDayOfMonthFull(_subscription.until.date()))); + _description.setText(st::defaultTextStyle, _subscription.title); + } + const auto descriptionPhotoId = (!_entry.subscriptionUntil.isNull()) + ? _entry.photoId + : _subscription.photoId; + if (descriptionPhotoId) { + _descriptionThumbnail = Ui::MakePhotoThumbnail( + _context.session->data().photo(descriptionPhotoId), + {}); + _descriptionThumbnail->subscribeToUpdates([this] { + const auto thumbnailSide = st::defaultTextStyle.font->height; + _descriptionThumbnailCache = Images::Round( + _descriptionThumbnail->image(thumbnailSide), + ImageRoundRadius::Large); + if (_context.customEmojiRepaint) { + _context.customEmojiRepaint(); + } + }); } auto &manager = _context.session->data().customEmojiManager(); if (_entry) { @@ -894,23 +944,40 @@ const Data::SubscriptionEntry &CreditsRow::subscription() const { } QString CreditsRow::generateName() { - return _entry.title.isEmpty() ? _name : _entry.title; + return (!_entry.title.isEmpty() && !_entry.subscriptionUntil.isNull()) + ? _name + : _entry.title.isEmpty() + ? _name + : _entry.title; } PaintRoundImageCallback CreditsRow::generatePaintUserpicCallback(bool force) { return _paintUserpicCallback; } +[[nodiscard]] QString RightActionText(const Data::SubscriptionEntry &s) { + return s.cancelledByBot + ? tr::lng_credits_subscription_status_off_by_bot_right(tr::now) + : s.cancelled + ? tr::lng_credits_subscription_status_off_right(tr::now) + : s.expired + ? tr::lng_credits_subscription_status_none_right(tr::now) + : QString(); +} + QSize CreditsRow::rightActionSize() const { if (_rightLabel) { return _rightLabel->size; - } else if (_subscription.cancelled || _subscription.expired) { - const auto text = _subscription.cancelled - ? tr::lng_credits_subscription_status_off_right(tr::now) - : tr::lng_credits_subscription_status_none_right(tr::now); - return QSize( - st::contactsStatusFont->width(text) + st::boxRowPadding.right(), - _rowHeight); + } else if (const auto t = RightActionText(_subscription); !t.isEmpty()) { + const auto lines = t.split('\n'); + auto maxWidth = 0; + for (const auto &line : lines) { + const auto width = st::contactsStatusFont->width(line); + if (width > maxWidth) { + maxWidth = width; + } + } + return QSize(maxWidth + st::boxRowPadding.right(), _rowHeight); } else if (_subscription || _entry) { return QSize( _rightText.maxWidth() + st::boxRowPadding.right() / 2, @@ -940,18 +1007,31 @@ void CreditsRow::rightActionPaint( const auto rightSkip = st::boxRowPadding.right(); if (_rightLabel) { return _rightLabel->draw(p, x, y, _rowHeight); - } else if (_subscription.cancelled || _subscription.expired) { + } else if (const auto t = RightActionText(_subscription); !t.isEmpty()) { const auto &statusFont = st::contactsStatusFont; y += _rowHeight / 2; p.setFont(statusFont); p.setPen(st::attentionButtonFg); - p.drawTextRight( - rightSkip, - y - statusFont->height / 2, - outerWidth, - _subscription.expired - ? tr::lng_credits_subscription_status_none_right(tr::now) - : tr::lng_credits_subscription_status_off_right(tr::now)); + + const auto lines = t.split('\n'); + if (lines.size() > 1) { + const auto rect = QRect(x, 0, outerWidth - x, _rowHeight); + const auto lineHeight = statusFont->height; + const auto totalHeight = lines.size() * lineHeight; + auto startY = rect.top() + + (rect.height() - totalHeight) / 2 + + statusFont->ascent; + + for (const auto &line : lines) { + const auto lineWidth = statusFont->width(line); + const auto startX = rect.left() + + (rect.width() - lineWidth) / 2; + p.drawText(startX, startY, line); + startY += lineHeight; + } + return; + } + p.drawTextRight(rightSkip, y - statusFont->height / 2, outerWidth, t); return; } y += _rowHeight / 2; @@ -969,6 +1049,43 @@ void CreditsRow::rightActionPaint( }); } +void CreditsRow::paintStatusText( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int available, + int outer, + bool selected) { + PeerListRow::paintStatusText(p, st, x, y, available, outer, selected); + p.setPen(st.nameFg); + if (!_descriptionThumbnailCache.isNull()) { + const auto thumbnailSide = _descriptionThumbnailCache.width() + / style::DevicePixelRatio(); + const auto thumbnailSpace = st::lineWidth * 4 + thumbnailSide; + p.drawImage( + x, + y - thumbnailSide, + _descriptionThumbnailCache); + x += thumbnailSpace; + outer -= thumbnailSpace; + available -= thumbnailSpace; + } + _description.draw(p, { + .position = QPoint(x, y - _description.minHeight()), + .outerWidth = outer, + .availableWidth = available, + .elisionLines = 1, + }); +} + +const style::PeerListItem &CreditsRow::computeSt( + const style::PeerListItem &st) const { + return (!_subscription || !_subscription.title.isEmpty()) + ? st + : st::boostsListBox.item; +} + class CreditsController final : public PeerListController { public: explicit CreditsController(CreditsDescriptor d); @@ -1010,7 +1127,7 @@ CreditsController::CreditsController(CreditsDescriptor d) .session = _session, .customEmojiRepaint = [] {}, }) { - PeerListController::setStyleOverrides(&st::boostsListBox); + PeerListController::setStyleOverrides(&st::creditsHistoryEntriesList); } Main::Session &CreditsController::session() const { @@ -1052,7 +1169,9 @@ void CreditsController::applySlice(const Data::CreditsStatusSlice &slice) { .entry = i, .subscription = s, .context = _context, - .rowHeight = computeListSt().item.height, + .rowHeight = ((!s || !s.title.isEmpty()) + ? computeListSt().item + : st::boostsListBox.item).height, .updateCallback = [=](not_null row) { delegate()->peerListUpdateRow(row); }, @@ -1239,28 +1358,36 @@ void AddCreditsHistoryList( not_null bot, bool in, bool out, - bool subscription) { + bool subs) { struct State final { - State( - CreditsDescriptor d, - std::shared_ptr show) - : delegate(std::move(show)) - , controller(std::move(d)) { + State(CreditsDescriptor d) : controller(std::move(d)) { } - PeerListContentDelegateShow delegate; + std::optional creditsDelegate; + std::optional subscriptionDelegate; CreditsController controller; }; const auto state = container->lifetime().make_state( - CreditsDescriptor{ firstSlice, callback, bot, in, out, subscription }, - show); + CreditsDescriptor{ firstSlice, callback, bot, in, out, subs }); + if (subs) { + state->subscriptionDelegate.emplace(); + state->subscriptionDelegate->setUiShow(show); + state->subscriptionDelegate->setContent(container->add( + object_ptr(container, &state->controller))); + state->controller.setDelegate(&(*state->subscriptionDelegate)); + } else { + state->creditsDelegate.emplace(show); + state->creditsDelegate->setContent(container->add( + object_ptr(container, &state->controller))); + state->controller.setDelegate(&(*state->creditsDelegate)); + } - state->delegate.setContent(container->add( - object_ptr(container, &state->controller))); - state->controller.setDelegate(&state->delegate); - - const auto wrap = AddShowMoreButton( - container, - tr::lng_stories_show_more()); + const auto wrap = container->add( + object_ptr>( + container, + CreateShowMoreButton(container, tr::lng_stories_show_more())), + subs + ? QMargins() + : QMargins(0, -st::settingsButton.padding.top(), 0, 0)); const auto showMore = [=] { if (!state->controller.skipRequest()) { @@ -1277,16 +1404,11 @@ void AddCreditsHistoryList( not_null*> AddShowMoreButton( not_null container, rpl::producer title) { - const auto wrap = container->add( + return container->add( object_ptr>( container, - object_ptr( - container, - std::move(title), - st::statisticsShowMoreButton)), + CreateShowMoreButton(container, std::move(title))), { 0, -st::settingsButton.padding.top(), 0, 0 }); - Ui::AddToggleUpDownArrowToMoreButton(wrap->entity()); - return wrap; } } // namespace Info::Statistics diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index dadb510b3..0d6c091b3 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -643,12 +643,13 @@ void ConfirmEmojiStatusBox( done(true); }); box->addButton(tr::lng_cancel(), [=] { - const auto was = *set; box->closeBox(); - if (!was) { + }); + box->boxClosing() | rpl::start_with_next([=] { + if (!*set) { done(false); } - }); + }, box->lifetime()); } class BotAction final : public Ui::Menu::ItemBase { @@ -954,7 +955,12 @@ void WebViewInstance::resolve() { requestSimple(); }); }, [&](WebViewSourceLinkApp data) { - resolveApp(data.appname, data.token, !_context.maySkipConfirmation); + resolveApp( + data.appname, + data.token, + (_context.maySkipConfirmation + ? ConfirmType::None + : ConfirmType::Always)); }, [&](WebViewSourceLinkBotProfile) { confirmOpen([=] { requestMain(); @@ -1002,14 +1008,14 @@ bool WebViewInstance::openAppFromBotMenuLink() { if (appname.isEmpty()) { return false; } - resolveApp(appname, params.value(u"startapp"_q), true); + resolveApp(appname, params.value(u"startapp"_q), ConfirmType::Once); return true; } void WebViewInstance::resolveApp( const QString &appname, const QString &startparam, - bool forceConfirmation) { + ConfirmType confirmType) { const auto already = _session->data().findBotApp(_bot->id, appname); _requestId = _session->api().request(MTPmessages_GetBotApp( MTP_inputBotAppShortName( @@ -1029,8 +1035,11 @@ void WebViewInstance::resolveApp( close(); return; } - const auto confirm = data.is_inactive() || forceConfirmation; + const auto confirm = data.is_inactive() + || (confirmType != ConfirmType::None); const auto writeAccess = result.data().is_request_write_access(); + const auto forceConfirmation = data.is_inactive() + || (confirmType == ConfirmType::Always); // Check if this app can be added to main menu. // On fail it'll still be opened. @@ -1043,7 +1052,7 @@ void WebViewInstance::resolveApp( } else if (confirm) { confirmAppOpen(writeAccess, [=](bool allowWrite) { requestApp(allowWrite); - }); + }, forceConfirmation); } else { requestApp(false); } @@ -1090,10 +1099,18 @@ void WebViewInstance::confirmOpen(Fn done) { void WebViewInstance::confirmAppOpen( bool writeAccess, - Fn done) { + Fn done, + bool forceConfirmation) { + if (!forceConfirmation + && (_bot->isVerified() + || _session->local().isBotTrustedOpenWebView(_bot->id))) { + done(writeAccess); + return; + } _parentShow->show(Box([=](not_null box) { const auto allowed = std::make_shared(); const auto callback = [=](Fn close) { + _session->local().markBotTrustedOpenWebView(_bot->id); done((*allowed) && (*allowed)->checked()); close(); }; diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index febf74dfa..f0cb146a7 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -230,12 +230,20 @@ private: void requestWithMenuAdd(); void maybeChooseAndRequestButton(PeerTypes supported); + enum class ConfirmType : uchar { + Always, + Once, + None, + }; void resolveApp( const QString &appname, const QString &startparam, - bool forceConfirmation); + ConfirmType confirmType); void confirmOpen(Fn done); - void confirmAppOpen(bool writeAccess, Fn done); + void confirmAppOpen( + bool writeAccess, + Fn done, + bool forceConfirmation); struct ShowArgs { QString url; diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 7941404c4..382226840 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -456,6 +456,8 @@ void Form::requestForm() { const auto amount = tlPrices.empty() ? 0 : tlPrices.front().data().vamount().v; + const auto subscriptionPeriod + = data.vinvoice().data().vsubscription_period().value_or(0); if (currency != ::Ui::kCreditsCurrency || !amount) { using Type = Error::Type; _updates.fire(Error{ Type::Form, u"Bad Stars Form."_q }); @@ -467,6 +469,7 @@ void Form::requestForm() { .credits = amount, .currency = currency, .amount = amount, + .subscriptionPeriod = subscriptionPeriod, }; const auto formData = CreditsFormData{ .id = _id, diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index 9b0d38d4d..d93fce51f 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -170,6 +170,7 @@ struct InvoiceCredits { uint64 amount = 0; bool extended = false; PeerId giftPeerId = PeerId(0); + int subscriptionPeriod = 0; }; struct InvoiceStarGift { diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp index 6f348df4f..0b9e292fd 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -94,14 +94,14 @@ void ProcessCreditsPayment( Ui::SendCreditsBox, form, [=] { - *unsuccessful = false; - if (const auto widget = fireworks.data()) { - Ui::StartFireworks(widget); - } - if (const auto onstack = maybeReturnToBot) { - onstack(CheckoutResult::Paid); - } - })); + *unsuccessful = false; + if (const auto widget = fireworks.data()) { + Ui::StartFireworks(widget); + } + if (const auto onstack = maybeReturnToBot) { + onstack(CheckoutResult::Paid); + } + })); box->boxClosing() | rpl::start_with_next([=] { crl::on_main([=] { if (*unsuccessful) { diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index b4cd7396d..f22b94a7c 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -191,6 +191,19 @@ void Credits::setupSubscriptions(not_null container) { fill(std::move(d)); }); } + { + using Rebuilder = Data::Session::CreditsSubsRebuilder; + using RebuilderPtr = std::shared_ptr; + const auto rebuilder = content->lifetime().make_state( + self->owner().createCreditsSubsRebuilder()); + rebuilder->get()->events( + ) | rpl::start_with_next([=](Data::CreditsStatusSlice slice) { + while (content->count()) { + delete content->widgetAt(0); + } + fill(std::move(slice)); + }, content->lifetime()); + } } void Credits::setupHistory(not_null container) { diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index fc1b1d8e8..4a190dc3a 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "payments/payments_checkout_process.h" #include "payments/payments_form.h" +#include "payments/payments_non_panel_process.h" #include "settings/settings_common_session.h" #include "settings/settings_credits.h" #include "statistics/widgets/chart_header_widget.h" @@ -346,6 +347,37 @@ void AddViewMediaHandler( }, thumb->lifetime()); } +void AddMiniStars( + not_null content, + not_null widget, + const style::UserpicButton &stUser, + int boxWidth, + float64 heightRatio) { + using ColoredMiniStars = Ui::Premium::ColoredMiniStars; + const auto stars = widget->lifetime().make_state( + widget, + false, + Ui::Premium::MiniStars::Type::BiStars); + stars->setColorOverride(Ui::Premium::CreditsIconGradientStops()); + widget->resize( + boxWidth - stUser.photoSize, + stUser.photoSize * heightRatio); + content->sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + widget->moveToLeft(stUser.photoSize / 2, 0); + const auto starsRect = Rect(widget->size()); + stars->setPosition(starsRect.topLeft()); + stars->setSize(starsRect.size()); + widget->lower(); + }, widget->lifetime()); + widget->paintRequest( + ) | rpl::start_with_next([=](const QRect &r) { + auto p = QPainter(widget); + p.fillRect(r, Qt::transparent); + stars->paint(p); + }, widget->lifetime()); +} + } // namespace SubscriptionRightLabel PaintSubscriptionRightLabelCallback( @@ -664,31 +696,12 @@ void BoostCreditsBox( { const auto &stUser = st::premiumGiftsUserpicButton; const auto widget = content->add(object_ptr(content)); - using ColoredMiniStars = Ui::Premium::ColoredMiniStars; - const auto stars = widget->lifetime().make_state( - widget, - false, - Ui::Premium::MiniStars::Type::BiStars); - stars->setColorOverride(Ui::Premium::CreditsIconGradientStops()); - widget->resize( - st::boxWidth - stUser.photoSize, - stUser.photoSize * 1.3); + AddMiniStars(content, widget, stUser, st::boxWidth, 1.3); const auto svg = std::make_shared( Ui::Premium::ColorizedSvg( Ui::Premium::CreditsIconGradientStops())); - content->sizeValue( - ) | rpl::start_with_next([=](const QSize &size) { - widget->moveToLeft(stUser.photoSize / 2, 0); - const auto starsRect = Rect(widget->size()); - stars->setPosition(starsRect.topLeft()); - stars->setSize(starsRect.size()); - widget->lower(); - }, widget->lifetime()); - widget->paintRequest( - ) | rpl::start_with_next([=](const QRect &r) { + widget->paintRequest() | rpl::start_with_next([=](const QRect &r) { auto p = QPainter(widget); - p.fillRect(r, Qt::transparent); - stars->paint(p); svg->render( &p, QRectF( @@ -790,6 +803,28 @@ void BoostCreditsBox( }, button->lifetime()); } +void ProcessReceivedSubscriptions( + QPointer weak, + not_null session) { + const auto rebuilder = session->data().activeCreditsSubsRebuilder(); + if (const auto strong = weak.data()) { + if (!rebuilder) { + return strong->closeBox(); + } + const auto api + = strong->lifetime().make_state( + session->user(), + true, + true); + api->requestSubscriptions({}, [=](Data::CreditsStatusSlice first) { + rebuilder->fire(std::move(first)); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }); + } +} + void ReceiptCreditsBox( not_null box, not_null controller, @@ -820,6 +855,7 @@ void ReceiptCreditsBox( const auto nonConvertible = (gotStarGift && !e.starsConverted); box->setStyle(st::giveawayGiftCodeBox); + box->setWidth(st::boxWideWidth); box->setNoContentMargin(true); const auto content = box->verticalLayout(); @@ -851,6 +887,20 @@ void ReceiptCreditsBox( content, GenericEntryPhoto(content, callback, stUser.photoSize))); AddViewMediaHandler(thumb->entity(), controller, e); + } else if (s.photoId || (e.photoId && !e.subscriptionUntil.isNull())) { + if (!(s.cancelled || s.expired || s.cancelledByBot)) { + const auto widget = Ui::CreateChild(content); + AddMiniStars(content, widget, stUser, st::boxWideWidth, 1.5); + } + const auto photoId = s.photoId ? s.photoId : e.photoId; + const auto callback = [=](Fn update) { + return Ui::GenerateCreditsPaintEntryCallback( + session->data().photo(photoId), + std::move(update)); + }; + content->add(object_ptr>( + content, + GenericEntryPhoto(content, callback, stUser.photoSize))); } else if (peer && !e.gift) { if (e.subscriptionUntil.isNull() && s.until.isNull()) { content->add(object_ptr>( @@ -950,11 +1000,13 @@ void ReceiptCreditsBox( box, object_ptr( box, - rpl::single(!s.until.isNull() + rpl::single(!s.title.isEmpty() + ? s.title + : !s.until.isNull() ? tr::lng_credits_box_subscription_title(tr::now) : isPrize ? tr::lng_credits_box_history_entry_giveaway_name(tr::now) - : !e.subscriptionUntil.isNull() + : (!e.subscriptionUntil.isNull() && e.title.isEmpty()) ? tr::lng_credits_box_history_entry_subscription(tr::now) : !e.title.isEmpty() ? e.title @@ -1270,16 +1322,48 @@ void ReceiptCreditsBox( st::creditsBoxAboutDivider))); } if (s) { + const auto user = peer ? peer->asUser() : nullptr; + const auto bot = (user && !user->isSelf()) ? user : nullptr; + const auto toCancel = !s.expired && !s.cancelled && !s.cancelledByBot; + if (toCancel) { + Ui::AddSkip(content); + } Ui::AddSkip(content); auto label = object_ptr( box, - s.cancelled + (s.cancelledByBot && bot) + ? tr::lng_credits_subscription_off_by_bot_about( + lt_bot, + rpl::single(bot->name())) + : toCancel + ? tr::lng_credits_subscription_on_button() + : s.cancelled ? tr::lng_credits_subscription_off_about() : tr::lng_credits_subscription_on_about( lt_date, rpl::single(langDayOfMonthFull(s.until.date()))), st::creditsBoxAboutDivider); - if (s.cancelled) { + if (toCancel) { + label->setClickHandlerFilter([=]( + const auto &, + Qt::MouseButton button) { + if (button != Qt::LeftButton) { + return false; + } + const auto done = [=, weak = Ui::MakeWeak(box)] { + ProcessReceivedSubscriptions(weak, session); + }; + const auto fail = [=, s = box->uiShow()](const QString &e) { + s->showToast(e); + }; + Api::EditCreditsSubscription(session, s.id, true, done, fail); + return true; + }); + label->setMarkedText( + Ui::Text::Link( + tr::lng_credits_subscription_on_button(tr::now), + u"internal:"_q)); + } else if (s.cancelled || s.cancelledByBot) { label->setTextColorOverride(st::menuIconAttentionColor->c); } box->addRow( @@ -1290,41 +1374,26 @@ void ReceiptCreditsBox( if (e.peerType == Data::CreditsHistoryEntry::PeerType::PremiumBot) { const auto widget = Ui::CreateChild(content); - using ColoredMiniStars = Ui::Premium::ColoredMiniStars; - const auto stars = widget->lifetime().make_state( - widget, - false, - Ui::Premium::MiniStars::Type::BiStars); - stars->setColorOverride(Ui::Premium::CreditsIconGradientStops()); - widget->resize( - st::boxWidth - stUser.photoSize, - stUser.photoSize * 2); - content->sizeValue( - ) | rpl::start_with_next([=](const QSize &size) { - widget->moveToLeft(stUser.photoSize / 2, 0); - const auto starsRect = Rect(widget->size()); - stars->setPosition(starsRect.topLeft()); - stars->setSize(starsRect.size()); - widget->lower(); - }, widget->lifetime()); - widget->paintRequest( - ) | rpl::start_with_next([=](const QRect &r) { - auto p = QPainter(widget); - p.fillRect(r, Qt::transparent); - stars->paint(p); - }, widget->lifetime()); + AddMiniStars(content, widget, stUser, st::boxWideWidth, 2); } + const auto rejoinByApi = base::unixtime::serialize(s.until) + > base::unixtime::now(); + const auto rejoinByInvite = !s.inviteHash.isEmpty(); + const auto rejoinBySlug = !s.slug.isEmpty(); const auto toRenew = (s.cancelled || s.expired) - && !s.inviteHash.isEmpty(); - const auto toCancel = !toRenew && s; + && (rejoinByApi || rejoinByInvite) + && !s.cancelledByBot; + const auto toRejoin = (s.cancelled || s.expired) + && rejoinBySlug + && !s.cancelledByBot; auto confirmText = rpl::conditional( state->confirmButtonBusy.value(), rpl::single(QString()), (toRenew ? tr::lng_credits_subscription_off_button() - : toCancel - ? tr::lng_credits_subscription_on_button() + : toRejoin + ? tr::lng_credits_subscription_off_rejoin_button() : (canConvert || couldConvert || nonConvertible) ? (e.savedToProfile ? tr::lng_gift_display_on_page_hide() @@ -1372,45 +1441,57 @@ void ReceiptCreditsBox( save, done); } + } else if (toRejoin) { + if (const auto window = weakWindow.get()) { + const auto finish = [=](Payments::CheckoutResult&&) { + ProcessReceivedSubscriptions(weak, session); + }; + Payments::CheckoutProcess::Start( + &window->session(), + s.slug, + [](auto) {}, + Payments::ProcessNonPanelPaymentFormFactory( + window, + finish)); + } } else if (toRenew && s.expired) { Api::CheckChatInvite(controller, s.inviteHash, nullptr, [=] { - if (const auto strong = weak.data()) { - strong->closeBox(); - } + ProcessReceivedSubscriptions(weak, session); }); } else { - using Flag = MTPpayments_ChangeStarsSubscription::Flag; - session->api().request( - MTPpayments_ChangeStarsSubscription( - MTP_flags(Flag::f_canceled), - MTP_inputPeerSelf(), - MTP_string(s.id), - MTP_bool(toCancel) - )).done([=] { - if (const auto strong = weak.data()) { - strong->closeBox(); - } - }).fail([=, show = box->uiShow()](const MTP::Error &error) { + const auto done = [=] { + ProcessReceivedSubscriptions(weak, session); + }; + const auto fail = [=, show = box->uiShow()](const QString &e) { if (const auto strong = weak.data()) { state->confirmButtonBusy = false; } - show->showToast(error.type()); - }).send(); + show->showToast(e); + }; + Api::EditCreditsSubscription(session, s.id, false, done, fail); } }; + const auto willBusy = toRejoin + || (peer + && (toRenew || canConvert || couldConvert || nonConvertible)); + if (willBusy) { + const auto close = Ui::CreateChild( + content, + st::boxTitleClose); + close->setClickedCallback([=] { box->closeBox(); }); + content->widthValue() | rpl::start_with_next([=](int) { + close->moveToRight(0, 0); + }, content->lifetime()); + } + const auto button = box->addButton(std::move(confirmText), [=] { if (state->confirmButtonBusy.current() || state->convertButtonBusy.current()) { return; } - state->confirmButtonBusy = true; - if (peer - && (toRenew - || toCancel - || canConvert - || couldConvert - || nonConvertible)) { + if (willBusy) { + state->confirmButtonBusy = true; send(); } else { box->closeBox(); @@ -1424,7 +1505,7 @@ void ReceiptCreditsBox( AddChildToWidgetCenter(button, loadingAnimation); loadingAnimation->showOn(state->confirmButtonBusy.value()); } - const auto buttonWidth = st::boxWidth + const auto buttonWidth = st::boxWideWidth - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding); button->widthValue() | rpl::filter([=] { diff --git a/Telegram/SourceFiles/ui/color_indices.style b/Telegram/SourceFiles/ui/color_indices.style new file mode 100644 index 000000000..3acd347ab --- /dev/null +++ b/Telegram/SourceFiles/ui/color_indices.style @@ -0,0 +1,16 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ + +colorIndexRed: 0; +colorIndexOrange: 1; +colorIndexGreen: 2; +colorIndexSea: 3; +colorIndexBlue: 4; +colorIndexPurple: 5; +colorIndexPink: 6; +colorIndexYellow: 7; diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 734b2a2f0..84008e849 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -156,3 +156,20 @@ giftListAbout: FlatLabel(defaultFlatLabel) { giftListAboutMargin: margins(12px, 24px, 12px, 24px); giftBoxEmojiToggleTop: 7px; giftBoxLimitTop: 28px; + +creditsHistoryEntriesList: PeerList(defaultPeerList) { + padding: margins( + 0px, + 7px, + 0px, + 7px); + item: PeerListItem(defaultPeerListItem) { + height: 66px; + photoPosition: point(18px, 6px); + namePosition: point(70px, 6px); + statusPosition: point(70px, 43px); + photoSize: 42px; + } +} + +subscriptionCreditsBadgePadding: margins(10px, 1px, 8px, 3px); diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index 76d899c76..d79741299 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -533,7 +533,7 @@ Fn)> PaintPreviewCallback( extended, std::move(update)); }; - } else if (entry.photoId) { + } else if (entry.photoId && entry.subscriptionUntil.isNull()) { const auto photo = session->data().photo(entry.photoId); return [=](Fn update) { return GenerateCreditsPaintEntryCallback( diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp index ac6f6dcbd..b2286be5f 100644 --- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp +++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp @@ -57,6 +57,26 @@ ChatsFiltersTabs::ChatsFiltersTabs( Ui::DiscreteSlider::setSelectOnPress(false); } +bool ChatsFiltersTabs::setSectionsAndCheckChanged( + std::vector &§ions) { + const auto &was = sectionsRef(); + const auto changed = [&] { + if (was.size() != sections.size()) { + return true; + } + for (auto i = 0; i < sections.size(); i++) { + if (was[i].label.toString() != sections[i]) { + return true; + } + } + return false; + }(); + if (changed) { + Ui::DiscreteSlider::setSections(std::move(sections)); + } + return changed; +} + int ChatsFiltersTabs::centerOfSection(int section) const { const auto widths = countSectionsWidths(0); auto result = 0; diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h index c66818fbd..9bd4cf67e 100644 --- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h +++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h @@ -27,6 +27,8 @@ public: not_null parent, const style::SettingsSlider &st); + bool setSectionsAndCheckChanged(std::vector &§ions); + [[nodiscard]] int centerOfSection(int section) const; void fitWidthToSections(); void setUnreadCount(int index, int unreadCount, bool muted); diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp index fe8710f30..2c4472e79 100644 --- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp +++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp @@ -293,15 +293,18 @@ not_null AddChatFiltersTabsStrip( if ((list.size() <= 1 && !slider->width()) || state->ignoreRefresh) { return; } + const auto sectionsChanged = slider->setSectionsAndCheckChanged( + ranges::views::all( + list + ) | ranges::views::transform([](const Data::ChatFilter &filter) { + return filter.title().isEmpty() + ? tr::lng_filters_all_short(tr::now) + : filter.title(); + }) | ranges::to_vector); + if (!sectionsChanged) { + return; + } state->rebuildLifetime.destroy(); - auto sections = ranges::views::all( - list - ) | ranges::views::transform([](const Data::ChatFilter &filter) { - return filter.title().isEmpty() - ? tr::lng_filters_all_short(tr::now) - : filter.title(); - }) | ranges::to_vector; - slider->setSections(std::move(sections)); slider->fitWidthToSections(); { const auto reorderAll = session->user()->isPremium(); diff --git a/Telegram/SourceFiles/ui/widgets/peer_bubble.cpp b/Telegram/SourceFiles/ui/widgets/peer_bubble.cpp new file mode 100644 index 000000000..87ac897d3 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/peer_bubble.cpp @@ -0,0 +1,72 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/widgets/peer_bubble.h" + +#include "data/data_peer.h" +#include "info/profile/info_profile_values.h" +#include "ui/controls/userpic_button.h" +#include "ui/painter.h" +#include "ui/rect.h" +#include "ui/widgets/labels.h" +#include "styles/style_boxes.h" +#include "styles/style_channel_earn.h" +#include "styles/style_chat.h" +#include "styles/style_layers.h" + +namespace Ui { + +object_ptr CreatePeerBubble( + not_null parent, + not_null peer) { + auto owned = object_ptr(parent); + const auto peerBubble = owned.data(); + peerBubble->setAttribute(Qt::WA_TransparentForMouseEvents); + const auto left = Ui::CreateChild( + peerBubble, + peer, + st::uploadUserpicButton); + const auto right = Ui::CreateChild( + peerBubble, + Info::Profile::NameValue(peer), + st::channelEarnSemiboldLabel); + const auto padding = st::chatGiveawayPeerPadding + + QMargins(st::chatGiveawayPeerPadding.left(), 0, 0, 0); + rpl::combine( + left->sizeValue(), + right->sizeValue() + ) | rpl::start_with_next([=]( + const QSize &leftSize, + const QSize &rightSize) { + peerBubble->resize( + leftSize.width() + rightSize.width() + rect::m::sum::h(padding), + leftSize.height()); + left->moveToLeft(0, 0); + right->moveToRight(padding.right() + st::lineWidth, padding.top()); + const auto maxRightSize = parent->width() + - rect::m::sum::h(st::boxRowPadding) + - rect::m::sum::h(padding) + - leftSize.width(); + if ((rightSize.width() > maxRightSize) && (maxRightSize > 0)) { + right->resizeToWidth(maxRightSize); + } + }, peerBubble->lifetime()); + peerBubble->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(peerBubble); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(st::windowBgOver); + const auto rect = peerBubble->rect(); + const auto radius = rect.height() / 2; + p.drawRoundedRect(rect, radius, radius); + }, peerBubble->lifetime()); + + return owned; +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/peer_bubble.h b/Telegram/SourceFiles/ui/widgets/peer_bubble.h new file mode 100644 index 000000000..9c0b657d9 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/peer_bubble.h @@ -0,0 +1,26 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +template +class object_ptr; + +class PeerData; + +namespace Ui { +class RpWidget; +class VerticalLayout; +} // namespace Ui + +namespace Ui { + +[[nodiscard]] object_ptr CreatePeerBubble( + not_null parent, + not_null peer); + +} // namespace Ui diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 54c15bbe6..4ef4df316 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -281,7 +281,7 @@ private: void addInfo(); void addStoryArchive(); void addNewWindow(); - void addToggleFolder(); + void addToggleFolder(bool onlyForChannels); void addToggleUnreadMark(); void addToggleArchive(); void addClearHistory(); @@ -620,12 +620,16 @@ void Filler::addStoryArchive() { }, &st::menuIconStoriesArchiveSection); } -void Filler::addToggleFolder() { +void Filler::addToggleFolder(bool onlyForChannels) { const auto controller = _controller; const auto history = _request.key.history(); if (_topic || !history || !history->owner().chatsFilters().has()) { return; } + if (onlyForChannels + && (!history->peer->isChannel() || !history->inChatList())) { + return; + } _addAction(PeerMenuCallback::Args{ .text = tr::lng_filters_menu_add(tr::now), .handler = nullptr, @@ -1409,7 +1413,7 @@ void Filler::fillContextMenuActions() { addToggleMuteSubmenu(false); addToggleUnreadMark(); addToggleTopicClosed(); - addToggleFolder(); + addToggleFolder(false); if (const auto user = _peer->asUser()) { if (!user->isContact()) { addBlockUser(); @@ -1459,6 +1463,7 @@ void Filler::fillProfileActions() { addToggleTopicClosed(); addViewDiscussion(); addExportChat(); + addToggleFolder(true); addBlockUser(); addReport(); addLeaveChat(); @@ -2070,7 +2075,17 @@ QPointer ShowForwardMessagesBox( class ListBox final : public PeerListBox { public: - using PeerListBox::PeerListBox; + ListBox( + QWidget *parent, + std::unique_ptr controller, + Fn)> init) + : PeerListBox( + parent, + std::move(controller), + [=](not_null box) { + init(static_cast(box.get())); + }) { + } void setBottomSkip(int bottomSkip) { PeerListBox::setInnerBottomSkip(bottomSkip); @@ -2095,9 +2110,21 @@ QPointer ShowForwardMessagesBox( _forwardOptions = forwardOptions; } + not_null peerListContent() const { + return PeerListBox::content(); + } + + void setFilterId(FilterId filterId) { + _filterId = filterId; + } + [[nodiscard]] FilterId filterId() const { + return _filterId; + } + private: rpl::event_stream<> _focusRequests; Ui::ForwardOptions _forwardOptions; + FilterId _filterId = 0; }; @@ -2115,6 +2142,12 @@ QPointer ShowForwardMessagesBox( }) { } + std::unique_ptr createRestoredRow( + not_null peer) override final { + return ChooseRecipientBoxController::createRow( + peer->owner().history(peer)); + } + using PeerListController::setSearchNoResultsText; void rowClicked(not_null row) override final { @@ -2167,166 +2200,48 @@ QPointer ShowForwardMessagesBox( base::unique_qptr menu; }; - const auto applyFilter = [=](not_null box, FilterId id) { + const auto applyFilter = [=](not_null box, FilterId id) { box->scrollToY(0); auto &filters = session->data().chatsFilters(); const auto &list = filters.list(); if (list.size() <= 1) { return; } - const auto pinnedList = [&]( - not_null list, - bool foundSelf) { - const auto pinned = list->pinned()->order(); - auto peers = std::vector>(); - peers.reserve(pinned.size()); - for (const auto &pin : pinned) { - if (!foundSelf && pin.peer()->isSelf()) { - peers.insert(peers.begin(), pin.peer()); - foundSelf = true; - } else { - peers.push_back(pin.peer()); - } - } - if (!foundSelf) { - peers.insert(peers.begin(), session->user()); - } - return peers; - }; - const auto folder = session->data().folderLoaded( - Data::Folder::kId); - const auto pinned = pinnedList( - id - ? filters.chatsList(id) - : session->data().chatsList(nullptr), - !!id); - const auto pinnedInFolder = (!id && folder) - ? pinnedList(folder->chatsList(), true) - : std::vector>(); - box->peerListSortRows([&]( - const PeerListRow &r1, - const PeerListRow &r2) { - { // Pinned to top. - auto it1 = pinned.end(); - auto it2 = pinned.end(); - for (auto it = pinned.begin(); it != pinned.end(); ++it) { - if ((*it) == r1.peer()) { - it1 = it; - } - if ((*it) == r2.peer()) { - it2 = it; - } - if (it1 != pinned.end() && it2 != pinned.end()) { - break; - } - } - if (it1 == pinned.end() && it2 != pinned.end()) { - return false; - } else if (it2 == pinned.end() && it1 != pinned.end()) { - return true; - } else if (it1 != pinned.end() && it2 != pinned.end()) { - return it1 < it2; - } - } - { // Pinned to bottom. - const auto &indexed = session->data().contactsNoChatsList(); - auto it1 = indexed->end(); - auto it2 = indexed->end(); - for (auto it = indexed->begin(); it != indexed->end(); ++it) { - if (it->get()->key().peer() == r1.peer()) { - it1 = it; - } - if (it->get()->key().peer() == r2.peer()) { - it2 = it; - } - if (it1 != indexed->end() && it2 != indexed->end()) { - break; - } - } - if (it1 == indexed->end() && it2 != indexed->end()) { - return true; - } else if (it2 == indexed->end() && it1 != indexed->end()) { - return false; - } else if (it1 != indexed->end() && it2 != indexed->end()) { - return it1 > it2; - } - } - if (folder) { - const auto pinned1 = ranges::find(pinnedInFolder, r1.peer()); - const auto pinned2 = ranges::find(pinnedInFolder, r2.peer()); - const auto isPinned1 = pinned1 != pinnedInFolder.end(); - const auto isPinned2 = pinned2 != pinnedInFolder.end(); - if (isPinned1 && isPinned2) { - return pinned1 < pinned2; - } - - const auto &indexed = folder->chatsList()->indexed(); - auto it1 = indexed->end(); - auto it2 = indexed->end(); - for (auto it = indexed->begin(); it != indexed->end(); ++it) { - if (it->get()->key().peer() == r1.peer()) { - it1 = it; - } - if (it->get()->key().peer() == r2.peer()) { - it2 = it; - } - if (it1 != indexed->end() && it2 != indexed->end()) { - break; - } - } - const auto isFoldered1 = it1 != indexed->end(); - const auto isFoldered2 = it2 != indexed->end(); - if (isPinned1 && !isPinned2) { - return isFoldered2; - } - if (isPinned2 && !isPinned1) { - return !isFoldered1; - } - if (!isPinned1 && !isPinned2) { - if (!isFoldered1 && isFoldered2) { - return true; - } else if (!isFoldered2 && isFoldered1) { - return false; - } else if (isFoldered1 && isFoldered2) { - return it1 < it2; - } - } - } - const auto history1 = session->data().history(r1.peer()); - const auto history2 = session->data().history(r2.peer()); - const auto date1 = history1->lastMessage() - ? history1->lastMessage()->date() - : TimeId(0); - const auto date2 = history2->lastMessage() - ? history2->lastMessage()->date() - : TimeId(0); - return date1 > date2; - }); - const auto filter = ranges::find( - list, - id, - &Data::ChatFilter::id); - if (filter == list.end()) { + if (box->filterId() == id) { return; } - box->peerListPartitionRows([&](const PeerListRow &row) { - const auto rowPtr = const_cast(&row); - if (!filter->id()) { - box->peerListSetRowHidden(rowPtr, false); - } else { - const auto result = filter->contains( - session->data().history(row.peer())); - box->peerListSetRowHidden(rowPtr, !result); + box->setFilterId(id); + + using SavedState = PeerListController::SavedStateBase; + auto state = std::make_unique(); + state->controllerState = std::make_unique(); + + const auto addList = [&](auto chats) { + for (const auto &row : chats->all()) { + if (const auto history = row->history()) { + state->list.push_back(history->peer); + } } - return false; - }); - box->peerListRefreshRows(); + }; + + if (!id) { + state->list.push_back(session->user()); + addList(session->data().chatsList()->indexed()); + const auto folderId = Data::Folder::kId; + if (const auto folder = session->data().folderLoaded(folderId)) { + addList(folder->chatsList()->indexed()); + } + addList(session->data().contactsNoChatsList()); + } else { + addList(session->data().chatsFilters().chatsList(id)->indexed()); + } + box->peerListContent()->restoreState(std::move(state)); }; const auto state = [&] { auto controller = std::make_unique(session); const auto controllerRaw = controller.get(); - auto init = [=](not_null box) { + auto init = [=](not_null box) { controllerRaw->setSearchNoResultsText( tr::lng_bot_chats_not_found(tr::now)); box->setSpecialTabMode(true); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 95b46dff1..593608cfb 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1874,6 +1874,10 @@ bool SessionController::switchInlineQuery( int(textWithTags.text.size()), Ui::kQFixedMax }; + if (to.currentReplyTo.messageId.msg == to.currentReplyTo.topicRootId + && to.currentReplyTo.quote.empty()) { + to.currentReplyTo.messageId.msg = MsgId(); + } auto draft = std::make_unique( textWithTags, to.currentReplyTo, diff --git a/Telegram/build/version b/Telegram/build/version index ff4691843..66fe3884c 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5008002 +AppVersion 5008003 AppVersionStrMajor 5.8 -AppVersionStrSmall 5.8.2 -AppVersionStr 5.8.2 +AppVersionStrSmall 5.8.3 +AppVersionStr 5.8.3 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 5.8.2 +AppVersionOriginal 5.8.3 diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index ec75a29e1..eb3287cd5 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -21,6 +21,7 @@ set(style_files ui/chat/chat.style ui/effects/credits.style ui/effects/premium.style + ui/color_indices.style boxes/boxes.style dialogs/dialogs.style chat_helpers/chat_helpers.style diff --git a/changelog.txt b/changelog.txt index a6704ef0b..22c02f612 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,10 @@ +5.8.3 (23.11.24) + +- Ctrl+Click on Reply in groups to reply in another chat. +- Fix freeze on forward messages box opening. +- Improve phrases in bot subscriptions. +- Several bugfixes. + 5.8.2 (19.11.24) - Improve bottom label color in mini apps.