diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 5ded5a040..6ac646b3c 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -94,6 +94,7 @@ jobs: -D CMAKE_CXX_FLAGS="-Werror" \ -D CMAKE_EXE_LINKER_FLAGS="-s" \ -D TDESKTOP_API_TEST=ON \ + -D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \ $DEFINE @@ -115,7 +116,7 @@ jobs: run: | cd $REPO_NAME/out/Debug mkdir artifact - mv Telegram artifact/ + mv {Telegram,Updater} artifact/ - uses: actions/upload-artifact@master if: env.UPLOAD_ARTIFACT == 'true' name: Upload artifact. diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 96bfe6e13..2a570aef7 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -115,6 +115,7 @@ jobs: -D CMAKE_CXX_FLAGS="-Werror" \ -D CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO \ -D TDESKTOP_API_TEST=ON \ + -D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \ $DEFINE diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 53b517939..b42906c7c 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -169,6 +169,7 @@ jobs: %TDESKTOP_BUILD_GENERATOR% ^ %TDESKTOP_BUILD_ARCH% ^ %TDESKTOP_BUILD_API% ^ + -D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^ -D DESKTOP_APP_NO_PDB=ON ^ %TDESKTOP_BUILD_DEFINE% @@ -178,8 +179,10 @@ jobs: - name: Move artifact. if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly') run: | + set OUT=%TBUILD%\%REPO_NAME%\out\Debug mkdir artifact - move %TBUILD%\%REPO_NAME%\out\Debug\Telegram.exe artifact/ + move %OUT%\Telegram.exe artifact/ + move %OUT%\Updater.exe artifact/ - uses: actions/upload-artifact@master name: Upload artifact. if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly') diff --git a/LEGAL b/LEGAL index e7c429315..5d0f5e8a7 100644 --- a/LEGAL +++ b/LEGAL @@ -1,7 +1,7 @@ This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. -Copyright (c) 2014-2023 The Telegram Desktop Authors. +Copyright (c) 2014-2024 The Telegram Desktop Authors. Telegram Desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 5f3eb6ea2..806084ada 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -620,6 +620,10 @@ PRIVATE data/data_replies_list.h data/data_reply_preview.cpp data/data_reply_preview.h + data/data_saved_messages.cpp + data/data_saved_messages.h + data/data_saved_sublist.cpp + data/data_saved_sublist.h data/data_search_controller.cpp data/data_search_controller.h data/data_send_action.cpp @@ -866,6 +870,8 @@ PRIVATE history/view/history_view_sponsored_click_handler.h history/view/history_view_sticker_toast.cpp history/view/history_view_sticker_toast.h + history/view/history_view_sublist_section.cpp + history/view/history_view_sublist_section.h history/view/history_view_transcribe_button.cpp history/view/history_view_transcribe_button.h history/view/history_view_translate_bar.cpp @@ -967,6 +973,8 @@ PRIVATE info/profile/info_profile_values.h info/profile/info_profile_widget.cpp info/profile/info_profile_widget.h + info/saved/info_saved_sublists_widget.cpp + info/saved/info_saved_sublists_widget.h info/settings/info_settings_widget.cpp info/settings/info_settings_widget.h info/similar_channels/info_similar_channels_widget.cpp diff --git a/Telegram/Resources/animations/voice_ttl_idle.tgs b/Telegram/Resources/animations/voice_ttl_idle.tgs new file mode 100644 index 000000000..016611298 Binary files /dev/null and b/Telegram/Resources/animations/voice_ttl_idle.tgs differ diff --git a/Telegram/Resources/animations/voice_ttl_start.tgs b/Telegram/Resources/animations/voice_ttl_start.tgs new file mode 100644 index 000000000..835556dd0 Binary files /dev/null and b/Telegram/Resources/animations/voice_ttl_start.tgs differ diff --git a/Telegram/Resources/icons/dialogs/avatar_hidden.png b/Telegram/Resources/icons/dialogs/avatar_hidden.png new file mode 100644 index 000000000..e032148c8 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_hidden.png differ diff --git a/Telegram/Resources/icons/dialogs/avatar_hidden@2x.png b/Telegram/Resources/icons/dialogs/avatar_hidden@2x.png new file mode 100644 index 000000000..ef143054e Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_hidden@2x.png differ diff --git a/Telegram/Resources/icons/dialogs/avatar_hidden@3x.png b/Telegram/Resources/icons/dialogs/avatar_hidden@3x.png new file mode 100644 index 000000000..f6ef6b733 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_hidden@3x.png differ diff --git a/Telegram/Resources/icons/dialogs/avatar_notes.png b/Telegram/Resources/icons/dialogs/avatar_notes.png new file mode 100644 index 000000000..740d38910 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_notes.png differ diff --git a/Telegram/Resources/icons/dialogs/avatar_notes@2x.png b/Telegram/Resources/icons/dialogs/avatar_notes@2x.png new file mode 100644 index 000000000..98dc7c89c Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_notes@2x.png differ diff --git a/Telegram/Resources/icons/dialogs/avatar_notes@3x.png b/Telegram/Resources/icons/dialogs/avatar_notes@3x.png new file mode 100644 index 000000000..735961bd9 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_notes@3x.png differ diff --git a/Telegram/Resources/icons/info/info_media_saved.png b/Telegram/Resources/icons/info/info_media_saved.png new file mode 100644 index 000000000..0b3ebc5dd Binary files /dev/null and b/Telegram/Resources/icons/info/info_media_saved.png differ diff --git a/Telegram/Resources/icons/info/info_media_saved@2x.png b/Telegram/Resources/icons/info/info_media_saved@2x.png new file mode 100644 index 000000000..aa91e6fb8 Binary files /dev/null and b/Telegram/Resources/icons/info/info_media_saved@2x.png differ diff --git a/Telegram/Resources/icons/info/info_media_saved@3x.png b/Telegram/Resources/icons/info/info_media_saved@3x.png new file mode 100644 index 000000000..6c9814fe1 Binary files /dev/null and b/Telegram/Resources/icons/info/info_media_saved@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 9709b376a..db9eeb97e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -395,6 +395,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dlg_new_bot_name" = "Bot name"; "lng_no_chats" = "Your chats will be here"; "lng_no_chats_filter" = "No chats currently belong to this folder."; +"lng_no_saved_sublists" = "You can save messages from other chats here."; "lng_contacts_loading" = "Loading..."; "lng_contacts_not_found" = "No contacts found"; "lng_topics_not_found" = "No topics found."; @@ -1187,6 +1188,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_profile_common_groups#other" = "{count} groups in common"; "lng_profile_similar_channels#one" = "{count} similar channel"; "lng_profile_similar_channels#other" = "{count} similar channels"; +"lng_profile_saved_messages#one" = "{count} saved message"; +"lng_profile_saved_messages#other" = "{count} saved messages"; "lng_profile_participants_section" = "Members"; "lng_profile_subscribers_section" = "Subscribers"; "lng_profile_add_contact" = "Add Contact"; @@ -1712,6 +1715,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_ttl_video_received" = "{from} sent you a self-destructing video. Please view it on your mobile."; "lng_ttl_video_sent" = "You sent a self-destructing video."; "lng_ttl_video_expired" = "Video has expired"; +"lng_ttl_voice_sent" = "You sent a self-destructing voice messsage."; +"lng_ttl_voice_expired" = "Voice message expired"; +"lng_ttl_round_sent" = "You sent a self-destructing video message."; +"lng_ttl_round_expired" = "Round message expired"; "lng_profile_add_more_after_create" = "You will be able to add more members after you create the group."; "lng_profile_camera_title" = "Capture yourself"; @@ -2492,6 +2499,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_saved_short" = "Save"; "lng_saved_forward_here" = "Forward messages here for quick access"; "lng_saved_quote_here" = "Quote here to save"; +"lng_saved_open_chat" = "Open Chat"; +"lng_saved_open_channel" = "Open Channel"; +"lng_saved_open_group" = "Open Group"; +"lng_saved_about_hidden" = "Senders of this messages restricted to link their name when forwarding."; "lng_scheduled_messages" = "Scheduled Messages"; "lng_scheduled_messages_empty" = "No scheduled messages here yet..."; @@ -2518,6 +2529,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_comments_open_none" = "Leave a comment"; "lng_replies_view_original" = "View in chat"; "lng_replies_messages" = "Replies"; +"lng_hidden_author_messages" = "Author Hidden"; +"lng_my_notes" = "My Notes"; "lng_replies_discussion_started" = "Discussion started"; "lng_replies_no_comments" = "No comments here yet..."; diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc index 705b508a2..76ea1ef4a 100644 --- a/Telegram/Resources/qrc/telegram/animations.qrc +++ b/Telegram/Resources/qrc/telegram/animations.qrc @@ -11,5 +11,7 @@ ../../animations/ttl.tgs ../../animations/discussion.tgs ../../animations/stats.tgs + ../../animations/voice_ttl_idle.tgs + ../../animations/voice_ttl_start.tgs diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index b0314d210..14a581ce3 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.14.1.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 02853f16f..5c7f838b6 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 4,13,1,0 - PRODUCTVERSION 4,13,1,0 + FILEVERSION 4,14,1,0 + PRODUCTVERSION 4,14,1,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop" - VALUE "FileVersion", "4.13.1.0" - VALUE "LegalCopyright", "Copyright (C) 2014-2023" + VALUE "FileVersion", "4.14.1.0" + VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "4.13.1.0" + VALUE "ProductVersion", "4.14.1.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 4859e694e..e91f35158 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 4,13,1,0 - PRODUCTVERSION 4,13,1,0 + FILEVERSION 4,14,1,0 + PRODUCTVERSION 4,14,1,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop Updater" - VALUE "FileVersion", "4.13.1.0" - VALUE "LegalCopyright", "Copyright (C) 2014-2023" + VALUE "FileVersion", "4.14.1.0" + VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "4.13.1.0" + VALUE "ProductVersion", "4.14.1.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/_other/startup_task_win.cpp b/Telegram/SourceFiles/_other/startup_task_win.cpp index ece42d938..7207f77e7 100644 --- a/Telegram/SourceFiles/_other/startup_task_win.cpp +++ b/Telegram/SourceFiles/_other/startup_task_win.cpp @@ -6,6 +6,7 @@ For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include +#include #include #include diff --git a/Telegram/SourceFiles/api/api_messages_search.cpp b/Telegram/SourceFiles/api/api_messages_search.cpp index 09f9d6a21..15119b906 100644 --- a/Telegram/SourceFiles/api/api_messages_search.cpp +++ b/Telegram/SourceFiles/api/api_messages_search.cpp @@ -90,6 +90,7 @@ void MessagesSearch::searchRequest() { (_from ? _from->input : MTP_inputPeerEmpty()), + MTPInputPeer(), // saved_peer_id MTPint(), // top_msg_id MTP_inputMessagesFilterEmpty(), MTP_int(0), // min_date diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 0f4cbd894..897a78d72 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/mtproto_dc_options.h" #include "data/notify/data_notify_settings.h" #include "data/stickers/data_stickers.h" +#include "data/data_saved_messages.h" #include "data/data_session.h" #include "data/data_user.h" #include "data/data_chat.h" @@ -44,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_cloud_manager.h" #include "history/history.h" #include "history/history_item.h" +#include "history/history_item_helpers.h" #include "history/history_unread_things.h" #include "core/application.h" #include "storage/storage_account.h" @@ -1143,6 +1145,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { ? peerToMTP(_session->userPeerId()) : MTP_peerUser(d.vuser_id())), MTP_peerUser(d.vuser_id()), + MTPPeer(), // saved_peer_id d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(), MTP_long(d.vvia_bot_id().value_or_empty()), d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(), @@ -1174,6 +1177,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { d.vid(), MTP_peerUser(d.vfrom_id()), MTP_peerChat(d.vchat_id()), + MTPPeer(), // saved_peer_id d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(), MTP_long(d.vvia_bot_id().value_or_empty()), d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(), @@ -1234,11 +1238,12 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) { item->markMediaAndMentionRead(); _session->data().requestItemRepaint(item); - if (item->out() - && item->history()->peer->isUser() - && !requestingDifference()) { - item->history()->peer->asUser()->madeAction( - base::unixtime::now()); + if (item->out()) { + const auto user = item->history()->peer->asUser(); + if (user && !requestingDifference()) { + user->madeAction(base::unixtime::now()); + } + ClearMediaAsExpired(item); } } } else { @@ -2236,6 +2241,16 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } break; + case mtpc_updatePinnedSavedDialogs: { + session().data().savedMessages().apply( + update.c_updatePinnedSavedDialogs()); + } break; + + case mtpc_updateSavedDialogPinned: { + session().data().savedMessages().apply( + update.c_updateSavedDialogPinned()); + } break; + case mtpc_updateChannel: { auto &d = update.c_updateChannel(); if (const auto channel = session().data().channelLoaded(d.vchannel_id())) { diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 35522ae5f..fb74e03f6 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_folder.h" #include "data/data_forum_topic.h" #include "data/data_forum.h" +#include "data/data_saved_sublist.h" #include "data/data_search_controller.h" #include "data/data_scheduled_messages.h" #include "data/data_session.h" @@ -444,6 +445,26 @@ void ApiWrap::savePinnedOrder(not_null forum) { }).send(); } +void ApiWrap::savePinnedOrder(not_null saved) { + const auto &order = _session->data().pinnedChatsOrder(saved); + const auto input = [](Dialogs::Key key) { + if (const auto sublist = key.sublist()) { + return MTP_inputDialogPeer(sublist->peer()->input); + } + Unexpected("Key type in pinnedDialogsOrder()."); + }; + auto peers = QVector(); + peers.reserve(order.size()); + ranges::transform( + order, + ranges::back_inserter(peers), + input); + request(MTPmessages_ReorderPinnedSavedDialogs( + MTP_flags(MTPmessages_ReorderPinnedSavedDialogs::Flag::f_force), + MTP_vector(peers) + )).send(); +} + void ApiWrap::toggleHistoryArchived( not_null history, bool archived, diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 70ed1affa..7e44d460c 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -34,6 +34,7 @@ class Forum; class ForumTopic; class Thread; class Story; +class SavedMessages; } // namespace Data namespace InlineBots { @@ -152,6 +153,7 @@ public: void savePinnedOrder(Data::Folder *folder); void savePinnedOrder(not_null forum); + void savePinnedOrder(not_null saved); void toggleHistoryArchived( not_null history, bool archived, diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index 4f46697c1..db72d5690 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -57,7 +57,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { constexpr auto kMaxWallPaperSlugLength = 255; -constexpr auto kDefaultDimming = 50; [[nodiscard]] bool IsValidWallPaperSlug(const QString &slug) { if (slug.isEmpty() || slug.size() > kMaxWallPaperSlugLength) { diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index 144582a53..3bdaa932d 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -113,7 +113,8 @@ Base64UrlInput::Base64UrlInput( rpl::producer placeholder, const QString &val) : MaskedInputField(parent, st, std::move(placeholder), val) { - if (!QRegularExpression("^[a-zA-Z0-9_\\-]+$").match(val).hasMatch()) { + static const auto RegExp = QRegularExpression("^[a-zA-Z0-9_\\-]+$"); + if (!RegExp.match(val).hasMatch()) { setText(QString()); } } @@ -831,8 +832,9 @@ void ProxyBox::prepare() { connect(_host.data(), &HostInput::changed, [=] { Ui::PostponeCall(_host, [=] { const auto host = _host->getLastText().trimmed(); - static const auto mask = u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q; - const auto match = QRegularExpression(mask).match(host); + static const auto mask = QRegularExpression( + u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q); + const auto match = mask.match(host); if (_host->cursorPosition() == host.size() && match.hasMatch()) { const auto port = match.captured(1); @@ -1107,6 +1109,10 @@ void ProxiesBoxController::ShowApplyConfirmation( proxy.password = fields.value(u"secret"_q); } if (proxy) { + static const auto UrlStartRegExp = QRegularExpression( + "^https://", + QRegularExpression::CaseInsensitiveOption); + static const auto UrlEndRegExp = QRegularExpression("/$"); const auto displayed = "https://" + server + "/"; const auto parsed = QUrl::fromUserInput(displayed); const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed) @@ -1117,11 +1123,9 @@ void ProxiesBoxController::ShowApplyConfirmation( const auto displayServer = QString( displayUrl ).replace( - QRegularExpression( - "^https://", - QRegularExpression::CaseInsensitiveOption), + UrlStartRegExp, QString() - ).replace(QRegularExpression("/$"), QString()); + ).replace(UrlEndRegExp, QString()); const auto text = tr::lng_sure_enable_socks( tr::now, lt_server, diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 92c6862fd..b01d980d9 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -62,7 +62,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { -constexpr auto kDiscountDivider = 5.; constexpr auto kUserpicsMax = size_t(3); using GiftOption = Data::SubscriptionOption; @@ -383,7 +382,8 @@ void GiftsBox( not_null box, not_null controller, std::vector> users, - not_null api) { + not_null api, + const QString &ref) { Expects(!users.empty()); const auto boxWidth = st::boxWideWidth; @@ -592,7 +592,7 @@ void GiftsBox( Settings::AddSummaryPremium( content, controller, - u"gift"_q, + ref, std::move(buttonCallback)); } @@ -615,7 +615,7 @@ void GiftsBox( auto raw = Settings::CreateSubscribeButton({ controller, box, - [] { return u"gift"_q; }, + [=] { return ref; }, rpl::combine( state->buttonText.events(), state->confirmButtonBusy.value(), @@ -877,7 +877,7 @@ void GiftPremiumValidator::cancel() { _requestId = 0; } -void GiftPremiumValidator::showChoosePeerBox() { +void GiftPremiumValidator::showChoosePeerBox(const QString &ref) { if (_manyGiftsLifetime) { return; } @@ -937,7 +937,7 @@ void GiftPremiumValidator::showChoosePeerBox() { }) | ranges::to>>(); if (!users.empty()) { const auto giftBox = show->show( - Box(GiftsBox, _controller, users, api)); + Box(GiftsBox, _controller, users, api, ref)); giftBox->boxClosing( ) | rpl::start_with_next([=] { _manyGiftsLifetime.destroy(); diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h index 5a0bfb659..ed001200e 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.h +++ b/Telegram/SourceFiles/boxes/gift_premium_box.h @@ -34,7 +34,7 @@ public: GiftPremiumValidator(not_null controller); void showBox(not_null user); - void showChoosePeerBox(); + void showChoosePeerBox(const QString &ref); void cancel(); private: diff --git a/Telegram/SourceFiles/boxes/premium_limits_box.cpp b/Telegram/SourceFiles/boxes/premium_limits_box.cpp index 64452a906..60eb5ecde 100644 --- a/Telegram/SourceFiles/boxes/premium_limits_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_limits_box.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_channel.h" #include "data/data_forum.h" +#include "data/data_saved_messages.h" #include "data/data_session.h" #include "data/data_folder.h" #include "data/data_premium_limits.h" @@ -882,6 +883,18 @@ void PinsLimitBox( limits.dialogsPinnedPremium(), PinsCount(session->data().chatsList())); } +void SublistsPinsLimitBox( + not_null box, + not_null session) { + const auto limits = Data::PremiumLimits(session); + SimplePinsLimitBox( + box, + session, + "saved_dialog_pinned", + limits.savedSublistsPinnedDefault(), + limits.savedSublistsPinnedPremium(), + PinsCount(session->data().savedMessages().chatsList())); +} void ForumPinsLimitBox( not_null box, diff --git a/Telegram/SourceFiles/boxes/premium_limits_box.h b/Telegram/SourceFiles/boxes/premium_limits_box.h index 5a4fa8a0a..8640dc235 100644 --- a/Telegram/SourceFiles/boxes/premium_limits_box.h +++ b/Telegram/SourceFiles/boxes/premium_limits_box.h @@ -60,6 +60,9 @@ void PinsLimitBox( void ForumPinsLimitBox( not_null box, not_null forum); +void SublistsPinsLimitBox( + not_null box, + not_null session); void CaptionLimitBox( not_null box, not_null session, diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index 92240b424..06835d603 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -51,8 +51,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { constexpr auto kPremiumShift = 21. / 240; -constexpr auto kReactionsPerRow = 5; -constexpr auto kDisabledOpacity = 0.5; constexpr auto kToggleStickerTimeout = 2 * crl::time(1000); constexpr auto kStarOpacityOff = 0.1; constexpr auto kStarOpacityOn = 1.; diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 1ffcc1ddf..d1b3dbb37 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -848,7 +848,7 @@ void ShareBox::Inner::loadProfilePhotos(int yFrom) { if (!_chatsIndexed->empty()) { const auto index = yFrom / _rowHeight; auto i = _chatsIndexed->begin() - + std::min(index, _chatsIndexed->size());; + + std::min(index, _chatsIndexed->size()); for (auto end = _chatsIndexed->cend(); i != end; ++i) { if (((*i)->index() * _rowHeight) >= yTo) { break; diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp index 66a4e01f5..9eb53580f 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.cpp +++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp @@ -520,6 +520,7 @@ void BoxController::loadMoreRows() { MTP_inputPeerEmpty(), MTP_string(), // q MTP_inputPeerEmpty(), + MTPInputPeer(), // saved_peer_id MTPint(), // top_msg_id MTP_inputMessagesFilterPhoneCalls(MTP_flags(0)), MTP_int(0), // min_date diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp index 26a9cbed0..946c6dca8 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp @@ -321,9 +321,6 @@ void Viewport::RendererGL::init( _frameBuffer->bind(); _frameBuffer->allocate(kValues * sizeof(GLfloat)); _downscaleProgram.yuv420.emplace(); - const auto downscaleVertexSource = VertexShader({ - VertexPassTextureCoord(), - }); _downscaleVertexShader = LinkProgram( &*_downscaleProgram.yuv420, VertexShader({ diff --git a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp index 85fc07502..87ac643d3 100644 --- a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp +++ b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp @@ -454,8 +454,10 @@ void ChooseSourceProcess::fillSources() { auto screenIndex = 0; auto windowIndex = 0; + auto firstScreenSelected = false; const auto active = _delegate->chooseSourceActiveDeviceId(); const auto append = [&](const tgcalls::DesktopCaptureSource &source) { + const auto firstScreen = !source.isWindow() && !screenIndex; const auto title = !source.isWindow() ? tr::lng_group_call_screen_title( tr::now, @@ -471,6 +473,10 @@ void ChooseSourceProcess::fillSources() { if (!active.isEmpty() && active.toStdString() == id) { _selected = raw; raw->setActive(true); + } else if (active.isEmpty() && firstScreen) { + _selected = raw; + raw->setActive(true); + firstScreenSelected = true; } _sources.back()->activations( ) | rpl::filter([=] { @@ -489,6 +495,9 @@ void ChooseSourceProcess::fillSources() { for (const auto &source : windowsManager.sources()) { append(source); } + if (firstScreenSelected) { + updateButtonsVisibility(); + } } void ChooseSourceProcess::updateButtonsVisibility() { diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index cc8ab9064..57747616b 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -683,7 +683,8 @@ bool Application::eventFilter(QObject *object, QEvent *e) { } break; case QEvent::ThemeChange: { - if (Platform::IsLinux() && object == QGuiApplication::allWindows().first()) { + if (Platform::IsLinux() + && object == QGuiApplication::allWindows().constFirst()) { Core::App().refreshApplicationIcon(); Core::App().tray().updateIconCounters(); } diff --git a/Telegram/SourceFiles/core/launcher.cpp b/Telegram/SourceFiles/core/launcher.cpp index fa0a728fa..8c4f2c1af 100644 --- a/Telegram/SourceFiles/core/launcher.cpp +++ b/Telegram/SourceFiles/core/launcher.cpp @@ -499,7 +499,7 @@ uint64 Launcher::installationTag() const { } void Launcher::processArguments() { - enum class KeyFormat { + enum class KeyFormat { NoValues, OneValue, AllLeftValues, @@ -542,9 +542,13 @@ void Launcher::processArguments() { } } + static const auto RegExp = QRegularExpression("[^a-z0-9\\-_]"); gDebugMode = parseResult.contains("-debug"); - gKeyFile = parseResult.value("-key", {}).join(QString()).toLower(); - gKeyFile = gKeyFile.replace(QRegularExpression("[^a-z0-9\\-_]"), {}); + gKeyFile = parseResult + .value("-key", {}) + .join(QString()) + .toLower() + .replace(RegExp, {}); gLaunchMode = parseResult.contains("-autostart") ? LaunchModeAutoStart : parseResult.contains("-fixprevious") ? LaunchModeFixPrevious : parseResult.contains("-cleanup") ? LaunchModeCleanup diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 582a695fe..68844669d 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -845,6 +845,21 @@ bool ResolvePremiumOffer( return true; } +bool ResolvePremiumMultigift( + Window::SessionController *controller, + const Match &match, + const QVariant &context) { + if (!controller) { + return false; + } + const auto params = url_parse_params( + match->captured(1).mid(1), + qthelp::UrlParamNameTransform::ToLower); + controller->showGiftPremiumsBox(params.value(u"ref"_q, u"gift_url"_q)); + controller->window().activate(); + return true; +} + bool ResolveLoginCode( Window::SessionController *controller, const Match &match, @@ -968,6 +983,10 @@ const std::vector &LocalUrlHandlers() { u"premium_offer/?(\\?.+)?(#|$)"_q, ResolvePremiumOffer, }, + { + u"^premium_multigift/?\\?(.+)(#|$)"_q, + ResolvePremiumMultigift, + }, { u"^login/?(\\?code=([0-9]+))(&|$)"_q, ResolveLoginCode @@ -1092,7 +1111,7 @@ QString TryConvertUrlToLocal(QString url) { const auto base = u"tg://privatepost?channel="_q + channel; auto added = QString(); if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, privateMatch->captured(2))) { - added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1)).arg(threadPostMatch->captured(2)); + added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1), threadPostMatch->captured(2)); } else if (const auto postMatch = regex_match(u"^/(\\d+)(/?\\?|/?$)"_q, privateMatch->captured(2))) { added = u"&post="_q + postMatch->captured(1); } @@ -1122,7 +1141,7 @@ QString TryConvertUrlToLocal(QString url) { const auto base = u"tg://resolve?domain="_q + url_encode(usernameMatch->captured(1)); auto added = QString(); if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) { - added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1)).arg(threadPostMatch->captured(2)); + added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1), threadPostMatch->captured(2)); } else if (const auto postMatch = regex_match(u"^/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) { added = u"&post="_q + postMatch->captured(1); } else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) { diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp index 0b92376cd..6feacdb5b 100644 --- a/Telegram/SourceFiles/core/sandbox.cpp +++ b/Telegram/SourceFiles/core/sandbox.cpp @@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/local_url_handlers.h" #include "core/update_checker.h" #include "core/deadlock_detector.h" -#include "base/options.h" #include "base/timer.h" #include "base/concurrent_timer.h" #include "base/invoke_queued.h" @@ -38,24 +37,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Core { namespace { -base::options::toggle OptionForceWaylandFractionalScaling({ - .id = kOptionForceWaylandFractionalScaling, - .name = "Enable xdg-output fractional scaling", - .description = "Enable xdg-output based fractional scaling on Wayland. " - "This works without fractional-scale-v1 and without " - "precise High DPI scaling. " - "Requires Qt with Desktop App Toolkit patches.", - .defaultValue = true, - .scope = [] { -#ifdef DESKTOP_APP_QT_PATCHED - return Platform::IsWayland(); -#else // DESKTOP_APP_QT_PATCHED - return false; -#endif // !DESKTOP_APP_QT_PATCHED - }, - .restartRequired = true, -}); - QChar _toHex(ushort v) { v = v & 0x000F; return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v)); @@ -96,17 +77,12 @@ QString _escapeFrom7bit(const QString &str) { } // namespace -const char kOptionForceWaylandFractionalScaling[] = "force-wayland-fractional-scaling"; - bool Sandbox::QuitOnStartRequested = false; Sandbox::Sandbox(int &argc, char **argv) : QApplication(argc, argv) , _mainThreadId(QThread::currentThreadId()) { setQuitOnLastWindowClosed(false); - if (OptionForceWaylandFractionalScaling.value()) { - setProperty("_q_force_wayland_fractional_scale", true); - } } int Sandbox::start() { @@ -240,7 +216,7 @@ void Sandbox::setupScreenScale() { const auto logEnv = [](const char *name) { const auto value = qEnvironmentVariable(name); if (!value.isEmpty()) { - LOG(("%1: %2").arg(name).arg(value)); + LOG(("%1: %2").arg(name, value)); } }; logEnv("QT_DEVICE_PIXEL_RATIO"); diff --git a/Telegram/SourceFiles/core/sandbox.h b/Telegram/SourceFiles/core/sandbox.h index 39d16296c..4c15c2828 100644 --- a/Telegram/SourceFiles/core/sandbox.h +++ b/Telegram/SourceFiles/core/sandbox.h @@ -19,8 +19,6 @@ class QLockFile; namespace Core { -extern const char kOptionForceWaylandFractionalScaling[]; - class UpdateChecker; class Application; diff --git a/Telegram/SourceFiles/core/update_checker.cpp b/Telegram/SourceFiles/core/update_checker.cpp index caeabb21a..45a4fe1a7 100644 --- a/Telegram/SourceFiles/core/update_checker.cpp +++ b/Telegram/SourceFiles/core/update_checker.cpp @@ -241,7 +241,7 @@ QString FindUpdateFile() { } const auto list = updates.entryInfoList(QDir::Files); for (const auto &info : list) { - if (QRegularExpression( + static const auto RegExp = QRegularExpression( "^(" "tupdate|" "tx64upd|" @@ -250,7 +250,8 @@ QString FindUpdateFile() { "tlinuxupd|" ")\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption - ).match(info.fileName()).hasMatch()) { + ); + if (RegExp.match(info.fileName()).hasMatch()) { return info.absoluteFilePath(); } } diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 112f7da3f..38b22e247 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 = 4013001; -constexpr auto AppVersionStr = "4.13.1"; +constexpr auto AppVersion = 4014001; +constexpr auto AppVersionStr = "4.14.1"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/SourceFiles/data/data_download_manager.cpp b/Telegram/SourceFiles/data/data_download_manager.cpp index 1d7ea12af..cd736e9e7 100644 --- a/Telegram/SourceFiles/data/data_download_manager.cpp +++ b/Telegram/SourceFiles/data/data_download_manager.cpp @@ -889,7 +889,6 @@ not_null DownloadManager::generateItem( const auto replyTo = FullReplyTo(); const auto viaBotId = UserId(); const auto date = base::unixtime::now(); - const auto postAuthor = QString(); const auto caption = TextWithEntities(); const auto make = [&](const auto media) { return history->makeMessage( diff --git a/Telegram/SourceFiles/data/data_folder.cpp b/Telegram/SourceFiles/data/data_folder.cpp index bbd4ce987..76f599943 100644 --- a/Telegram/SourceFiles/data/data_folder.cpp +++ b/Telegram/SourceFiles/data/data_folder.cpp @@ -343,12 +343,6 @@ int Folder::storiesUnreadCount() const { return _storiesUnreadCount; } -void Folder::requestChatListMessage() { - if (!chatListMessageKnown()) { - owner().histories().requestDialogEntry(this); - } -} - TimeId Folder::adjustedChatListTimeId() const { return chatListTimeId(); } diff --git a/Telegram/SourceFiles/data/data_folder.h b/Telegram/SourceFiles/data/data_folder.h index 7008adac0..7f6347e93 100644 --- a/Telegram/SourceFiles/data/data_folder.h +++ b/Telegram/SourceFiles/data/data_folder.h @@ -49,9 +49,9 @@ public: Dialogs::BadgesState chatListBadgesState() const override; HistoryItem *chatListMessage() const override; bool chatListMessageKnown() const override; - void requestChatListMessage() override; const QString &chatListName() const override; const QString &chatListNameSortKey() const override; + int chatListNameVersion() const override; const base::flat_set &chatListNameWords() const override; const base::flat_set &chatListFirstLetters() const override; @@ -82,8 +82,6 @@ public: private: void indexNameParts(); - int chatListNameVersion() const override; - void reorderLastHistories(); void paintUserpic( diff --git a/Telegram/SourceFiles/data/data_forum_icons.cpp b/Telegram/SourceFiles/data/data_forum_icons.cpp index 1469cf559..c47c8e669 100644 --- a/Telegram/SourceFiles/data/data_forum_icons.cpp +++ b/Telegram/SourceFiles/data/data_forum_icons.cpp @@ -15,13 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" namespace Data { -namespace { - -constexpr auto kRefreshDefaultListEach = 60 * 60 * crl::time(1000); -constexpr auto kRecentRequestTimeout = 10 * crl::time(1000); -constexpr auto kMaxTimeout = 6 * 60 * 60 * crl::time(1000); - -} // namespace ForumIcons::ForumIcons(not_null owner) : _owner(owner) diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index 66430a0a3..275f38fcb 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.h +++ b/Telegram/SourceFiles/data/data_forum_topic.h @@ -98,6 +98,7 @@ public: void setRealRootId(MsgId realId); void readTillEnd(); + void requestChatListMessage(); void applyTopic(const MTPDforumTopic &data); @@ -109,9 +110,9 @@ public: Dialogs::BadgesState chatListBadgesState() const override; HistoryItem *chatListMessage() const override; bool chatListMessageKnown() const override; - void requestChatListMessage() override; const QString &chatListName() const override; const QString &chatListNameSortKey() const override; + int chatListNameVersion() const override; const base::flat_set &chatListNameWords() const override; const base::flat_set &chatListFirstLetters() const override; @@ -187,8 +188,6 @@ private: void allowChatListMessageResolve(); void resolveChatListMessageGroup(); - int chatListNameVersion() const override; - void subscribeToUnreadChanges(); [[nodiscard]] Dialogs::UnreadState unreadStateFor( int count, diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index f716984ad..e9c4dd66e 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -555,6 +555,10 @@ bool Media::hasSpoiler() const { return false; } +crl::time Media::ttlSeconds() const { + return 0; +} + bool Media::consumeMessageText(const TextWithEntities &text) { return false; } @@ -849,12 +853,14 @@ MediaFile::MediaFile( not_null parent, not_null document, bool skipPremiumEffect, - bool spoiler) + bool spoiler, + crl::time ttlSeconds) : Media(parent) , _document(document) , _emoji(document->sticker() ? document->sticker()->alt : QString()) , _skipPremiumEffect(skipPremiumEffect) -, _spoiler(spoiler) { +, _spoiler(spoiler) +, _ttlSeconds(ttlSeconds) { parent->history()->owner().registerDocumentItem(_document, parent); if (!_emoji.isEmpty()) { @@ -882,7 +888,8 @@ std::unique_ptr MediaFile::clone(not_null parent) { parent, _document, !_document->session().premium(), - _spoiler); + _spoiler, + _ttlSeconds); } DocumentData *MediaFile::document() const { @@ -1129,6 +1136,14 @@ bool MediaFile::hasSpoiler() const { return _spoiler; } +crl::time MediaFile::ttlSeconds() const { + return _ttlSeconds; +} + +bool MediaFile::allowsForward() const { + return !ttlSeconds(); +} + bool MediaFile::updateInlineResultMedia(const MTPMessageMedia &media) { if (media.type() != mtpc_messageMediaDocument) { return false; diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 2a4e18e90..b2323af8a 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -174,6 +174,7 @@ public: virtual bool dropForwardedInfo() const; virtual bool forceForwardedInfo() const; [[nodiscard]] virtual bool hasSpoiler() const; + [[nodiscard]] virtual crl::time ttlSeconds() const; [[nodiscard]] virtual bool consumeMessageText( const TextWithEntities &text); @@ -256,7 +257,8 @@ public: not_null parent, not_null document, bool skipPremiumEffect, - bool spoiler); + bool spoiler, + crl::time ttlSeconds); ~MediaFile(); std::unique_ptr clone(not_null parent) override; @@ -278,6 +280,8 @@ public: bool forwardedBecomesUnread() const override; bool dropForwardedInfo() const override; bool hasSpoiler() const override; + crl::time ttlSeconds() const override; + bool allowsForward() const override; bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; @@ -292,6 +296,9 @@ private: bool _skipPremiumEffect = false; bool _spoiler = false; + // Video (unsupported) / Voice / Round. + crl::time _ttlSeconds = 0; + }; class MediaContact final : public Media { diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 746407a5f..a6f033924 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_folder.h" #include "data/data_forum.h" #include "data/data_forum_topic.h" +#include "data/data_saved_messages.h" #include "data/data_session.h" #include "data/data_file_origin.h" #include "data/data_histories.h" @@ -1029,6 +1030,10 @@ bool PeerData::sharedMediaInfo() const { return isSelf() || isRepliesChat(); } +bool PeerData::savedSublistsInfo() const { + return isSelf() && owner().savedMessages().supported(); +} + bool PeerData::hasStoriesHidden() const { if (const auto user = asUser()) { return user->hasStoriesHidden(); diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 8f553f9db..0252063f4 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -165,6 +165,7 @@ public: virtual ~PeerData(); static constexpr auto kServiceNotificationsId = peerFromUser(777000); + static constexpr auto kSavedHiddenAuthorId = peerFromUser(2666000); [[nodiscard]] Data::Session &owner() const; [[nodiscard]] Main::Session &session() const; @@ -202,6 +203,7 @@ public: [[nodiscard]] bool isGigagroup() const; [[nodiscard]] bool isRepliesChat() const; [[nodiscard]] bool sharedMediaInfo() const; + [[nodiscard]] bool savedSublistsInfo() const; [[nodiscard]] bool hasStoriesHidden() const; void setStoriesHidden(bool hidden); @@ -212,6 +214,9 @@ public: [[nodiscard]] bool isServiceUser() const { return isUser() && !(id.value % 1000); } + [[nodiscard]] bool isSavedHiddenAuthor() const { + return (id == kSavedHiddenAuthorId); + } [[nodiscard]] Data::Forum *forum() const; [[nodiscard]] Data::ForumTopic *forumTopicFor(MsgId rootId) const; diff --git a/Telegram/SourceFiles/data/data_photo_media.cpp b/Telegram/SourceFiles/data/data_photo_media.cpp index 0b66e192a..af01588f4 100644 --- a/Telegram/SourceFiles/data/data_photo_media.cpp +++ b/Telegram/SourceFiles/data/data_photo_media.cpp @@ -226,7 +226,6 @@ bool PhotoMedia::setToClipboard() { if (fallback.isNull()) { return false; } - const auto bytes = imageBytes(large); auto mime = std::make_unique(); mime->setImageData(std::move(fallback)); if (auto bytes = imageBytes(large); !bytes.isEmpty()) { diff --git a/Telegram/SourceFiles/data/data_premium_limits.cpp b/Telegram/SourceFiles/data/data_premium_limits.cpp index 07a5124a6..443474040 100644 --- a/Telegram/SourceFiles/data/data_premium_limits.cpp +++ b/Telegram/SourceFiles/data/data_premium_limits.cpp @@ -141,6 +141,18 @@ int PremiumLimits::topicsPinnedCurrent() const { return appConfigLimit("topics_pinned_limit", 5); } +int PremiumLimits::savedSublistsPinnedDefault() const { + return appConfigLimit("saved_dialogs_pinned_limit_default", 5); +} +int PremiumLimits::savedSublistsPinnedPremium() const { + return appConfigLimit("saved_dialogs_pinned_limit_premium", 100); +} +int PremiumLimits::savedSublistsPinnedCurrent() const { + return isPremium() + ? savedSublistsPinnedPremium() + : savedSublistsPinnedDefault(); +} + int PremiumLimits::channelsPublicDefault() const { return appConfigLimit("channels_public_limit_default", 10); } diff --git a/Telegram/SourceFiles/data/data_premium_limits.h b/Telegram/SourceFiles/data/data_premium_limits.h index f9bd416d2..bc3c86d9f 100644 --- a/Telegram/SourceFiles/data/data_premium_limits.h +++ b/Telegram/SourceFiles/data/data_premium_limits.h @@ -59,6 +59,10 @@ public: [[nodiscard]] int topicsPinnedCurrent() const; + [[nodiscard]] int savedSublistsPinnedDefault() const; + [[nodiscard]] int savedSublistsPinnedPremium() const; + [[nodiscard]] int savedSublistsPinnedCurrent() const; + [[nodiscard]] int channelsPublicDefault() const; [[nodiscard]] int channelsPublicPremium() const; [[nodiscard]] int channelsPublicCurrent() const; diff --git a/Telegram/SourceFiles/data/data_saved_messages.cpp b/Telegram/SourceFiles/data/data_saved_messages.cpp new file mode 100644 index 000000000..695622bbe --- /dev/null +++ b/Telegram/SourceFiles/data/data_saved_messages.cpp @@ -0,0 +1,296 @@ +/* +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 "data/data_saved_messages.h" + +#include "apiwrap.h" +#include "data/data_peer.h" +#include "data/data_saved_sublist.h" +#include "data/data_session.h" +#include "history/history.h" +#include "history/history_item.h" +#include "main/main_session.h" + +namespace Data { +namespace { + +constexpr auto kPerPage = 50; +constexpr auto kFirstPerPage = 10; + +} // namespace + +SavedMessages::SavedMessages(not_null owner) +: _owner(owner) +, _chatsList( + &owner->session(), + FilterId(), + owner->maxPinnedChatsLimitValue(this)) +, _loadMore([=] { sendLoadMoreRequests(); }) { +} + +SavedMessages::~SavedMessages() = default; + +bool SavedMessages::supported() const { + return !_unsupported; +} + +Session &SavedMessages::owner() const { + return *_owner; +} + +Main::Session &SavedMessages::session() const { + return _owner->session(); +} + +not_null SavedMessages::chatsList() { + return &_chatsList; +} + +not_null SavedMessages::sublist(not_null peer) { + const auto i = _sublists.find(peer); + if (i != end(_sublists)) { + return i->second.get(); + } + return _sublists.emplace( + peer, + std::make_unique(peer)).first->second.get(); +} + +void SavedMessages::loadMore() { + _loadMoreScheduled = true; + _loadMore.call(); +} + +void SavedMessages::loadMore(not_null sublist) { + _loadMoreSublistsScheduled.emplace(sublist); + _loadMore.call(); +} + +void SavedMessages::sendLoadMore() { + if (_loadMoreRequestId || _chatsList.loaded()) { + return; + } else if (!_pinnedLoaded) { + loadPinned(); + } + _loadMoreRequestId = _owner->session().api().request( + MTPmessages_GetSavedDialogs( + MTP_flags(MTPmessages_GetSavedDialogs::Flag::f_exclude_pinned), + MTP_int(_offsetDate), + MTP_int(_offsetId), + _offsetPeer ? _offsetPeer->input : MTP_inputPeerEmpty(), + MTP_int(kPerPage), + MTP_long(0)) // hash + ).done([=](const MTPmessages_SavedDialogs &result) { + apply(result, false); + }).fail([=](const MTP::Error &error) { + if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) { + _unsupported = true; + } + _chatsList.setLoaded(); + _loadMoreRequestId = 0; + }).send(); +} + +void SavedMessages::loadPinned() { + if (_pinnedRequestId) { + return; + } + _pinnedRequestId = _owner->session().api().request( + MTPmessages_GetPinnedSavedDialogs() + ).done([=](const MTPmessages_SavedDialogs &result) { + apply(result, true); + }).fail([=](const MTP::Error &error) { + if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) { + _unsupported = true; + } else { + _pinnedLoaded = true; + } + _pinnedRequestId = 0; + }).send(); +} + +void SavedMessages::sendLoadMore(not_null sublist) { + if (_loadMoreRequests.contains(sublist) || sublist->isFullLoaded()) { + return; + } + const auto &list = sublist->messages(); + const auto offsetId = list.empty() ? MsgId(0) : list.back()->id; + const auto offsetDate = list.empty() ? MsgId(0) : list.back()->date(); + const auto limit = offsetId ? kPerPage : kFirstPerPage; + const auto requestId = _owner->session().api().request( + MTPmessages_GetSavedHistory( + sublist->peer()->input, + MTP_int(offsetId), + MTP_int(offsetDate), + MTP_int(0), // add_offset + MTP_int(limit), + MTP_int(0), // max_id + MTP_int(0), // min_id + MTP_long(0)) // hash + ).done([=](const MTPmessages_Messages &result) { + auto count = 0; + auto list = (const QVector*)nullptr; + result.match([](const MTPDmessages_channelMessages &) { + LOG(("API Error: messages.channelMessages in sublist.")); + }, [](const MTPDmessages_messagesNotModified &) { + LOG(("API Error: messages.messagesNotModified in sublist.")); + }, [&](const auto &data) { + owner().processUsers(data.vusers()); + owner().processChats(data.vchats()); + list = &data.vmessages().v; + if constexpr (MTPDmessages_messages::Is()) { + count = int(list->size()); + } else { + count = data.vcount().v; + } + }); + + _loadMoreRequests.remove(sublist); + if (!list) { + sublist->setFullLoaded(); + return; + } + auto items = std::vector>(); + items.reserve(list->size()); + for (const auto &message : *list) { + const auto item = owner().addNewMessage( + message, + {}, + NewMessageType::Existing); + if (item) { + items.push_back(item); + } + } + sublist->append(std::move(items), count); + if (result.type() == mtpc_messages_messages) { + sublist->setFullLoaded(); + } + }).fail([=](const MTP::Error &error) { + if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) { + _unsupported = true; + } + sublist->setFullLoaded(); + _loadMoreRequests.remove(sublist); + }).send(); + _loadMoreRequests[sublist] = requestId; +} + +void SavedMessages::apply( + const MTPmessages_SavedDialogs &result, + bool pinned) { + auto list = (const QVector*)nullptr; + result.match([](const MTPDmessages_savedDialogsNotModified &) { + LOG(("API Error: messages.savedDialogsNotModified.")); + }, [&](const auto &data) { + _owner->processUsers(data.vusers()); + _owner->processChats(data.vchats()); + _owner->processMessages( + data.vmessages(), + NewMessageType::Existing); + list = &data.vdialogs().v; + }); + if (pinned) { + _pinnedRequestId = 0; + _pinnedLoaded = true; + } else { + _loadMoreRequestId = 0; + } + if (!list) { + if (!pinned) { + _chatsList.setLoaded(); + } + return; + } + auto lastValid = false; + auto offsetDate = TimeId(); + auto offsetId = MsgId(); + auto offsetPeer = (PeerData*)nullptr; + const auto selfId = _owner->session().userPeerId(); + for (const auto &dialog : *list) { + const auto &data = dialog.data(); + const auto peer = _owner->peer(peerFromMTP(data.vpeer())); + const auto topId = MsgId(data.vtop_message().v); + if (const auto item = _owner->message(selfId, topId)) { + offsetPeer = peer; + offsetDate = item->date(); + offsetId = topId; + lastValid = true; + const auto entry = sublist(peer); + const auto entryPinned = pinned || data.is_pinned(); + entry->applyMaybeLast(item); + _owner->setPinnedFromEntryList(entry, entryPinned); + } else { + lastValid = false; + } + } + if (pinned) { + } else if (!lastValid) { + LOG(("API Error: Unknown message in the end of a slice.")); + _chatsList.setLoaded(); + } else if (result.type() == mtpc_messages_savedDialogs) { + _chatsList.setLoaded(); + } else if (offsetDate < _offsetDate + || (offsetDate == _offsetDate && offsetId == _offsetId && offsetPeer == _offsetPeer)) { + LOG(("API Error: Bad order in messages.savedDialogs.")); + _chatsList.setLoaded(); + } else { + _offsetDate = offsetDate; + _offsetId = offsetId; + _offsetPeer = offsetPeer; + } +} + +void SavedMessages::sendLoadMoreRequests() { + if (_loadMoreScheduled) { + sendLoadMore(); + } + for (const auto sublist : base::take(_loadMoreSublistsScheduled)) { + sendLoadMore(sublist); + } +} + +void SavedMessages::apply(const MTPDupdatePinnedSavedDialogs &update) { + const auto list = update.vorder(); + if (!list) { + loadPinned(); + return; + } + const auto &order = list->v; + const auto notLoaded = [&](const MTPDialogPeer &peer) { + return peer.match([&](const MTPDdialogPeer &data) { + const auto peer = _owner->peer(peerFromMTP(data.vpeer())); + return !_sublists.contains(peer); + }, [&](const MTPDdialogPeerFolder &data) { + LOG(("API Error: " + "updatePinnedSavedDialogs has folders.")); + return false; + }); + }; + if (!ranges::none_of(order, notLoaded)) { + loadPinned(); + } else { + _chatsList.pinned()->applyList(_owner, order); + _owner->notifyPinnedDialogsOrderUpdated(); + } +} + +void SavedMessages::apply(const MTPDupdateSavedDialogPinned &update) { + update.vpeer().match([&](const MTPDdialogPeer &data) { + const auto peer = _owner->peer(peerFromMTP(data.vpeer())); + const auto i = _sublists.find(peer); + if (i != end(_sublists)) { + const auto entry = i->second.get(); + _owner->setChatPinned(entry, FilterId(), update.is_pinned()); + } else { + loadPinned(); + } + }, [&](const MTPDdialogPeerFolder &data) { + DEBUG_LOG(("API Error: Folder in updateSavedDialogPinned.")); + }); +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_saved_messages.h b/Telegram/SourceFiles/data/data_saved_messages.h new file mode 100644 index 000000000..3e09f4db0 --- /dev/null +++ b/Telegram/SourceFiles/data/data_saved_messages.h @@ -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 +*/ +#pragma once + +#include "dialogs/dialogs_main_list.h" + +namespace Main { +class Session; +} // namespace Main + +namespace Data { + +class Session; +class SavedSublist; + +class SavedMessages final { +public: + explicit SavedMessages(not_null owner); + ~SavedMessages(); + + [[nodiscard]] bool supported() const; + + [[nodiscard]] Session &owner() const; + [[nodiscard]] Main::Session &session() const; + + [[nodiscard]] not_null chatsList(); + [[nodiscard]] not_null sublist(not_null peer); + + void loadMore(); + void loadMore(not_null sublist); + + void apply(const MTPDupdatePinnedSavedDialogs &update); + void apply(const MTPDupdateSavedDialogPinned &update); + +private: + void loadPinned(); + void apply(const MTPmessages_SavedDialogs &result, bool pinned); + + void sendLoadMore(); + void sendLoadMore(not_null sublist); + void sendLoadMoreRequests(); + + const not_null _owner; + + Dialogs::MainList _chatsList; + base::flat_map< + not_null, + std::unique_ptr> _sublists; + + base::flat_map, mtpRequestId> _loadMoreRequests; + mtpRequestId _loadMoreRequestId = 0; + mtpRequestId _pinnedRequestId = 0; + + TimeId _offsetDate = 0; + MsgId _offsetId = 0; + PeerData *_offsetPeer = nullptr; + + SingleQueuedInvokation _loadMore; + base::flat_set> _loadMoreSublistsScheduled; + bool _loadMoreScheduled = false; + + bool _pinnedLoaded = false; + bool _unsupported = false; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_saved_sublist.cpp b/Telegram/SourceFiles/data/data_saved_sublist.cpp new file mode 100644 index 000000000..2d129a5f6 --- /dev/null +++ b/Telegram/SourceFiles/data/data_saved_sublist.cpp @@ -0,0 +1,256 @@ +/* +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 "data/data_saved_sublist.h" + +#include "data/data_histories.h" +#include "data/data_peer.h" +#include "data/data_saved_messages.h" +#include "data/data_session.h" +#include "history/view/history_view_item_preview.h" +#include "history/history.h" +#include "history/history_item.h" + +namespace Data { + +SavedSublist::SavedSublist(not_null peer) +: Entry(&peer->owner(), Dialogs::Entry::Type::SavedSublist) +, _history(peer->owner().history(peer)) { +} + +SavedSublist::~SavedSublist() = default; + +not_null SavedSublist::history() const { + return _history; +} + +not_null SavedSublist::peer() const { + return _history->peer; +} + +bool SavedSublist::isHiddenAuthor() const { + return peer()->isSavedHiddenAuthor(); +} + +bool SavedSublist::isFullLoaded() const { + return (_flags & Flag::FullLoaded) != 0; +} + +auto SavedSublist::messages() const +-> const std::vector> & { + return _items; +} + +void SavedSublist::applyMaybeLast(not_null item, bool added) { + const auto before = []( + not_null a, + not_null b) { + return IsServerMsgId(a->id) + ? (IsServerMsgId(b->id) ? (a->id < b->id) : true) + : (IsServerMsgId(b->id) ? false : (a->id < b->id)); + }; + + if (_items.empty()) { + _items.push_back(item); + } else if (_items.front() == item) { + return; + } else if (!isFullLoaded() + && _items.size() == 1 + && before(_items.front(), item)) { + _items[0] = item; + } else if (before(_items.back(), item)) { + for (auto i = begin(_items); i != end(_items); ++i) { + if (item == *i) { + return; + } else if (before(*i, item)) { + _items.insert(i, item); + break; + } + } + } + if (added && _fullCount) { + ++*_fullCount; + } + if (_items.front() == item) { + setChatListTimeId(item->date()); + resolveChatListMessageGroup(); + } + _changed.fire({}); +} + +void SavedSublist::removeOne(not_null item) { + if (_items.empty()) { + return; + } + const auto last = (_items.front() == item); + const auto from = ranges::remove(_items, item); + const auto removed = end(_items) - from; + if (removed) { + _items.erase(from, end(_items)); + } + if (_fullCount) { + --*_fullCount; + } + if (last) { + if (_items.empty()) { + if (isFullLoaded()) { + updateChatListExistence(); + } else { + updateChatListEntry(); + crl::on_main(this, [=] { + owner().savedMessages().loadMore(this); + }); + } + } else { + setChatListTimeId(_items.front()->date()); + } + } + if (removed || _fullCount) { + _changed.fire({}); + } +} + +rpl::producer<> SavedSublist::changes() const { + return _changed.events(); +} + +std::optional SavedSublist::fullCount() const { + return isFullLoaded() ? int(_items.size()) : _fullCount; +} + +rpl::producer SavedSublist::fullCountValue() const { + return _changed.events_starting_with({}) | rpl::map([=] { + return fullCount(); + }) | rpl::filter_optional(); +} + +void SavedSublist::append( + std::vector> &&items, + int fullCount) { + _fullCount = fullCount; + if (items.empty()) { + setFullLoaded(); + } else if (_items.empty()) { + _items = std::move(items); + setChatListTimeId(_items.front()->date()); + _changed.fire({}); + } else if (_items.back()->id > items.front()->id) { + _items.insert(end(_items), begin(items), end(items)); + _changed.fire({}); + } else { + _items.insert(end(_items), begin(items), end(items)); + ranges::stable_sort( + _items, + ranges::greater(), + &HistoryItem::id); + ranges::unique(_items, ranges::greater(), &HistoryItem::id); + _changed.fire({}); + } +} + +void SavedSublist::setFullLoaded(bool loaded) { + if (loaded != isFullLoaded()) { + if (loaded) { + _flags |= Flag::FullLoaded; + if (_items.empty()) { + updateChatListExistence(); + } + } else { + _flags &= ~Flag::FullLoaded; + } + _changed.fire({}); + } +} + +int SavedSublist::fixedOnTopIndex() const { + return 0; +} + +bool SavedSublist::shouldBeInChatList() const { + return isPinnedDialog(FilterId()) || !_items.empty(); +} + +Dialogs::UnreadState SavedSublist::chatListUnreadState() const { + return {}; +} + +Dialogs::BadgesState SavedSublist::chatListBadgesState() const { + return {}; +} + +HistoryItem *SavedSublist::chatListMessage() const { + return _items.empty() ? nullptr : _items.front().get(); +} + +bool SavedSublist::chatListMessageKnown() const { + return true; +} + +const QString &SavedSublist::chatListName() const { + return _history->chatListName(); +} + +const base::flat_set &SavedSublist::chatListNameWords() const { + return _history->chatListNameWords(); +} + +const base::flat_set &SavedSublist::chatListFirstLetters() const { + return _history->chatListFirstLetters(); +} + +const QString &SavedSublist::chatListNameSortKey() const { + return _history->chatListNameSortKey(); +} + +int SavedSublist::chatListNameVersion() const { + return _history->chatListNameVersion(); +} + +void SavedSublist::paintUserpic( + Painter &p, + Ui::PeerUserpicView &view, + const Dialogs::Ui::PaintContext &context) const { + _history->paintUserpic(p, view, context); +} + +void SavedSublist::chatListPreloadData() { + peer()->loadUserpic(); + allowChatListMessageResolve(); +} + +void SavedSublist::allowChatListMessageResolve() { + if (_flags & Flag::ResolveChatListMessage) { + return; + } + _flags |= Flag::ResolveChatListMessage; + resolveChatListMessageGroup(); +} + +bool SavedSublist::hasOrphanMediaGroupPart() const { + if (isFullLoaded() || _items.size() != 1) { + return false; + } + return (_items.front()->groupId() != MessageGroupId()); +} + +void SavedSublist::resolveChatListMessageGroup() { + const auto item = chatListMessage(); + if (!(_flags & Flag::ResolveChatListMessage) + || !item + || !hasOrphanMediaGroupPart()) { + return; + } + // If we set a single album part, request the full album. + const auto withImages = !item->toPreview({ + .hideSender = true, + .hideCaption = true }).images.empty(); + if (withImages) { + owner().histories().requestGroupAround(item); + } +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_saved_sublist.h b/Telegram/SourceFiles/data/data_saved_sublist.h new file mode 100644 index 000000000..15c3428ff --- /dev/null +++ b/Telegram/SourceFiles/data/data_saved_sublist.h @@ -0,0 +1,85 @@ +/* +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 "dialogs/ui/dialogs_message_view.h" +#include "dialogs/dialogs_entry.h" + +class PeerData; +class History; + +namespace Data { + +class Session; + +class SavedSublist final : public Dialogs::Entry { +public: + explicit SavedSublist(not_null peer); + ~SavedSublist(); + + [[nodiscard]] not_null history() const; + [[nodiscard]] not_null peer() const; + [[nodiscard]] bool isHiddenAuthor() const; + [[nodiscard]] bool isFullLoaded() const; + + [[nodiscard]] auto messages() const + -> const std::vector> &; + void applyMaybeLast(not_null item, bool added = false); + void removeOne(not_null item); + void append(std::vector> &&items, int fullCount); + void setFullLoaded(bool loaded = true); + + [[nodiscard]] rpl::producer<> changes() const; + [[nodiscard]] std::optional fullCount() const; + [[nodiscard]] rpl::producer fullCountValue() const; + + [[nodiscard]] Dialogs::Ui::MessageView &lastItemDialogsView() { + return _lastItemDialogsView; + } + + int fixedOnTopIndex() const override; + bool shouldBeInChatList() const override; + Dialogs::UnreadState chatListUnreadState() const override; + Dialogs::BadgesState chatListBadgesState() const override; + HistoryItem *chatListMessage() const override; + bool chatListMessageKnown() const override; + const QString &chatListName() const override; + const QString &chatListNameSortKey() const override; + int chatListNameVersion() const override; + const base::flat_set &chatListNameWords() const override; + const base::flat_set &chatListFirstLetters() const override; + + void chatListPreloadData() override; + void paintUserpic( + Painter &p, + Ui::PeerUserpicView &view, + const Dialogs::Ui::PaintContext &context) const override; + +private: + enum class Flag : uchar { + ResolveChatListMessage = (1 << 0), + FullLoaded = (1 << 1), + }; + friend inline constexpr bool is_flag_type(Flag) { return true; } + using Flags = base::flags; + + bool hasOrphanMediaGroupPart() const; + void allowChatListMessageResolve(); + void resolveChatListMessageGroup(); + + const not_null _history; + + std::vector> _items; + std::optional _fullCount; + rpl::event_stream<> _changed; + Dialogs::Ui::MessageView _lastItemDialogsView; + Flags _flags; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.cpp b/Telegram/SourceFiles/data/data_scheduled_messages.cpp index 7f5821b7c..f122f05b9 100644 --- a/Telegram/SourceFiles/data/data_scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/data_scheduled_messages.cpp @@ -68,6 +68,7 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000); data.vid(), data.vfrom_id() ? *data.vfrom_id() : MTPPeer(), data.vpeer_id(), + data.vsaved_peer_id() ? *data.vsaved_peer_id() : MTPPeer(), data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(), MTP_long(data.vvia_bot_id().value_or_empty()), data.vreply_to() ? *data.vreply_to() : MTPMessageReplyHeader(), @@ -216,6 +217,7 @@ void ScheduledMessages::sendNowSimpleMessage( update.vid(), peerToMTP(local->from()->id), peerToMTP(history->peer->id), + MTPPeer(), // saved_peer_id MTPMessageFwdHeader(), MTPlong(), // via_bot_id replyHeader, diff --git a/Telegram/SourceFiles/data/data_search_controller.cpp b/Telegram/SourceFiles/data/data_search_controller.cpp index 08fae4983..688ce4ae9 100644 --- a/Telegram/SourceFiles/data/data_search_controller.cpp +++ b/Telegram/SourceFiles/data/data_search_controller.cpp @@ -97,6 +97,7 @@ std::optional PrepareSearchRequest( peer->input, MTP_string(query), MTP_inputPeerEmpty(), + MTPInputPeer(), // saved_peer_id MTP_int(topicRootId), filter, MTP_int(0), // min_date diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 1185501f0..3885b9155 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -60,6 +60,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_emoji_statuses.h" #include "data/data_forum_icons.h" #include "data/data_cloud_themes.h" +#include "data/data_saved_messages.h" +#include "data/data_saved_sublist.h" #include "data/data_stories.h" #include "data/data_streaming.h" #include "data/data_media_rotation.h" @@ -268,7 +270,8 @@ Session::Session(not_null session) , _forumIcons(std::make_unique(this)) , _notifySettings(std::make_unique(this)) , _customEmojiManager(std::make_unique(this)) -, _stories(std::make_unique(this)) { +, _stories(std::make_unique(this)) +, _savedMessages(std::make_unique(this)) { _cache->open(_session->local().cacheKey()); _bigFileCache->open(_session->local().cacheBigFileKey()); @@ -1724,6 +1727,11 @@ void Session::requestItemRepaint(not_null item) { topic->updateChatListEntry(); } } + if (const auto sublist = item->savedSublist()) { + if (sublist->lastItemDialogsView().dependsOn(item)) { + sublist->updateChatListEntry(); + } + } } rpl::producer> Session::itemRepaintRequest() const { @@ -2111,13 +2119,17 @@ void Session::applyDialog( setPinnedFromEntryList(folder, data.is_pinned()); } -bool Session::pinnedCanPin(not_null thread) const { - if (const auto topic = thread->asTopic()) { +bool Session::pinnedCanPin(not_null entry) const { + if (const auto sublist = entry->asSublist()) { + const auto saved = &savedMessages(); + return pinnedChatsOrder(saved).size() < pinnedChatsLimit(saved); + } else if (const auto topic = entry->asTopic()) { const auto forum = topic->forum(); return pinnedChatsOrder(forum).size() < pinnedChatsLimit(forum); + } else { + const auto folder = entry->folder(); + return pinnedChatsOrder(folder).size() < pinnedChatsLimit(folder); } - const auto folder = thread->folder(); - return pinnedChatsOrder(folder).size() < pinnedChatsLimit(folder); } bool Session::pinnedCanPin( @@ -2149,6 +2161,11 @@ int Session::pinnedChatsLimit(not_null forum) const { return limits.topicsPinnedCurrent(); } +int Session::pinnedChatsLimit(not_null saved) const { + const auto limits = Data::PremiumLimits(_session); + return limits.savedSublistsPinnedCurrent(); +} + rpl::producer Session::maxPinnedChatsLimitValue( Data::Folder *folder) const { // Premium limit from appconfig. @@ -2189,6 +2206,20 @@ rpl::producer Session::maxPinnedChatsLimitValue( }); } +rpl::producer Session::maxPinnedChatsLimitValue( + not_null saved) const { + // Premium limit from appconfig. + // We always use premium limit in the MainList limit producer, + // because it slices the list to that limit. We don't want to slice + // premium-ly added chats from the pinned list because of sync issues. + return rpl::single(rpl::empty_value()) | rpl::then( + _session->account().appConfig().refreshed() + ) | rpl::map([=] { + const auto limits = Data::PremiumLimits(_session); + return limits.savedSublistsPinnedPremium(); + }); +} + const std::vector &Session::pinnedChatsOrder( Data::Folder *folder) const { return chatsList(folder)->pinned()->order(); @@ -2204,6 +2235,11 @@ const std::vector &Session::pinnedChatsOrder( return forum->topicsList()->pinned()->order(); } +const std::vector &Session::pinnedChatsOrder( + not_null saved) const { + return saved->chatsList()->pinned()->order(); +} + void Session::clearPinnedChats(Data::Folder *folder) { chatsList(folder)->pinned()->clear(); } @@ -2220,7 +2256,7 @@ void Session::reorderTwoPinnedChats( ? topic->forum()->topicsList() : filterId ? chatsFilters().chatsList(filterId) - : chatsList(key1.entry()->folder()); + : chatsListFor(key1.entry()); list->pinned()->reorder(key1, key2); notifyPinnedDialogsOrderUpdated(); } @@ -4262,6 +4298,8 @@ not_null Session::chatsListFor( const auto topic = entry->asTopic(); return topic ? topic->forum()->topicsList() + : entry->asSublist() + ? _savedMessages->chatsList() : chatsList(entry->folder()); } @@ -4458,6 +4496,7 @@ void Session::insertCheckedServiceNotification( MTP_int(0), // Not used (would've been trimmed to 32 bits). peerToMTP(PeerData::kServiceNotificationsId), peerToMTP(PeerData::kServiceNotificationsId), + MTPPeer(), // saved_peer_id MTPMessageFwdHeader(), MTPlong(), // via_bot_id MTPMessageReplyHeader(), diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index ea93cc178..244fa1524 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -61,6 +61,7 @@ class GroupCall; class NotifySettings; class CustomEmojiManager; class Stories; +class SavedMessages; struct RepliesReadTillUpdate { FullMsgId id; @@ -137,6 +138,9 @@ public: [[nodiscard]] Stories &stories() const { return *_stories; } + [[nodiscard]] SavedMessages &savedMessages() const { + return *_savedMessages; + } [[nodiscard]] MsgId nextNonHistoryEntryId() { return ++_nonHistoryEntryId; @@ -345,25 +349,31 @@ public: const QVector &dialogs, std::optional count = std::nullopt); - [[nodiscard]] bool pinnedCanPin(not_null thread) const; + [[nodiscard]] bool pinnedCanPin(not_null entry) const; [[nodiscard]] bool pinnedCanPin( FilterId filterId, not_null history) const; [[nodiscard]] int pinnedChatsLimit(Folder *folder) const; [[nodiscard]] int pinnedChatsLimit(FilterId filterId) const; [[nodiscard]] int pinnedChatsLimit(not_null forum) const; + [[nodiscard]] int pinnedChatsLimit( + not_null saved) const; [[nodiscard]] rpl::producer maxPinnedChatsLimitValue( Folder *folder) const; [[nodiscard]] rpl::producer maxPinnedChatsLimitValue( FilterId filterId) const; [[nodiscard]] rpl::producer maxPinnedChatsLimitValue( not_null forum) const; + [[nodiscard]] rpl::producer maxPinnedChatsLimitValue( + not_null saved) const; [[nodiscard]] const std::vector &pinnedChatsOrder( Folder *folder) const; [[nodiscard]] const std::vector &pinnedChatsOrder( not_null forum) const; [[nodiscard]] const std::vector &pinnedChatsOrder( FilterId filterId) const; + [[nodiscard]] const std::vector &pinnedChatsOrder( + not_null saved) const; void setChatPinned(Dialogs::Key key, FilterId filterId, bool pinned); void setPinnedFromEntryList(Dialogs::Key key, bool pinned); void clearPinnedChats(Folder *folder); @@ -1041,6 +1051,7 @@ private: const std::unique_ptr _notifySettings; const std::unique_ptr _customEmojiManager; const std::unique_ptr _stories; + const std::unique_ptr _savedMessages; MsgId _nonHistoryEntryId = ServerMaxMsgId.bare + ScheduledMsgIdsRange; diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index 3073003a1..3e8ab6ac5 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -1360,7 +1360,6 @@ void Stories::sendIncrementViewsRequests() { return; } - auto ids = QVector(); struct Prepared { PeerId peer = 0; QVector ids; diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 8dd712d02..e8c9a33df 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -385,7 +385,7 @@ QString UserData::username() const { } QString UserData::editableUsername() const { - return _username.editableUsername();; + return _username.editableUsername(); } const std::vector &UserData::usernames() const { diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 123621b37..1f80c53f6 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -349,7 +349,6 @@ void CustomEmojiLoader::check() { const auto tag = _tag; const auto sizeOverride = int(_sizeOverride); const auto size = FrameSizeFromTag(_tag, _sizeOverride); - auto bytes = Lottie::ReadContent(data, filepath); auto loader = [=] { return std::make_unique( document, diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index d324ca44a..b42c490a3 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -347,6 +347,8 @@ dialogsForumIcon: ThreeStateIcon { dialogsArchiveUserpic: icon {{ "archive_userpic", historyPeerUserpicFg }}; dialogsRepliesUserpic: icon {{ "replies_userpic", historyPeerUserpicFg }}; dialogsInaccessibleUserpic: icon {{ "dialogs/inaccessible_userpic", historyPeerUserpicFg }}; +dialogsHiddenAuthorUserpic: icon {{ "dialogs/avatar_hidden", premiumButtonFg }}; +dialogsMyNotesUserpic: icon {{ "dialogs/avatar_notes", historyPeerUserpicFg }}; dialogsSendStateSkip: 20px; dialogsSendingIcon: ThreeStateIcon { diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp index f2d5ebcd4..26f6e1fcb 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_folder.h" #include "data/data_forum_topic.h" #include "data/data_chat_filters.h" +#include "data/data_saved_sublist.h" #include "core/application.h" #include "core/core_settings.h" #include "mainwidget.h" @@ -83,6 +84,8 @@ Entry::Entry(not_null owner, Type type) ? (Flag::IsThread | Flag::IsHistory) : (type == Type::ForumTopic) ? Flag::IsThread + : (type == Type::SavedSublist) + ? Flag::IsSavedSublist : Flag(0)) { } @@ -109,7 +112,7 @@ Data::Forum *Entry::asForum() { } Data::Folder *Entry::asFolder() { - return (_flags & Flag::IsThread) + return (_flags & (Flag::IsThread | Flag::IsSavedSublist)) ? nullptr : static_cast(this); } @@ -126,6 +129,12 @@ Data::ForumTopic *Entry::asTopic() { : nullptr; } +Data::SavedSublist *Entry::asSublist() { + return (_flags & Flag::IsSavedSublist) + ? static_cast(this) + : nullptr; +} + const History *Entry::asHistory() const { return const_cast(this)->asHistory(); } @@ -146,6 +155,10 @@ const Data::ForumTopic *Entry::asTopic() const { return const_cast(this)->asTopic(); } +const Data::SavedSublist *Entry::asSublist() const { + return const_cast(this)->asSublist(); +} + void Entry::pinnedIndexChanged(FilterId filterId, int was, int now) { if (!filterId && session().supportMode()) { // Force reorder in support mode. diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h index 3ad4281f3..56e84f48c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.h +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h @@ -25,6 +25,7 @@ class Session; class Forum; class Folder; class ForumTopic; +class SavedSublist; } // namespace Data namespace Ui { @@ -151,6 +152,7 @@ public: History, Folder, ForumTopic, + SavedSublist, }; Entry(not_null owner, Type type); virtual ~Entry(); @@ -163,12 +165,14 @@ public: Data::Folder *asFolder(); Data::Thread *asThread(); Data::ForumTopic *asTopic(); + Data::SavedSublist *asSublist(); const History *asHistory() const; const Data::Forum *asForum() const; const Data::Folder *asFolder() const; const Data::Thread *asThread() const; const Data::ForumTopic *asTopic() const; + const Data::SavedSublist *asSublist() const; PositionChange adjustByPosInChatList( FilterId filterId, @@ -206,27 +210,29 @@ public: void setChatListTimeId(TimeId date); virtual void updateChatListExistence(); bool needUpdateInChatList() const; - virtual TimeId adjustedChatListTimeId() const; + [[nodiscard]] virtual TimeId adjustedChatListTimeId() const; - virtual int fixedOnTopIndex() const = 0; + [[nodiscard]] virtual int fixedOnTopIndex() const = 0; static constexpr auto kArchiveFixOnTopIndex = 1; static constexpr auto kTopPromotionFixOnTopIndex = 2; - virtual bool shouldBeInChatList() const = 0; - virtual UnreadState chatListUnreadState() const = 0; - virtual BadgesState chatListBadgesState() const = 0; - virtual HistoryItem *chatListMessage() const = 0; - virtual bool chatListMessageKnown() const = 0; - virtual void requestChatListMessage() = 0; - virtual const QString &chatListName() const = 0; - virtual const QString &chatListNameSortKey() const = 0; - virtual const base::flat_set &chatListNameWords() const = 0; - virtual const base::flat_set &chatListFirstLetters() const = 0; + [[nodiscard]] virtual bool shouldBeInChatList() const = 0; + [[nodiscard]] virtual UnreadState chatListUnreadState() const = 0; + [[nodiscard]] virtual BadgesState chatListBadgesState() const = 0; + [[nodiscard]] virtual HistoryItem *chatListMessage() const = 0; + [[nodiscard]] virtual bool chatListMessageKnown() const = 0; + [[nodiscard]] virtual const QString &chatListName() const = 0; + [[nodiscard]] virtual const QString &chatListNameSortKey() const = 0; + [[nodiscard]] virtual int chatListNameVersion() const = 0; + [[nodiscard]] virtual auto chatListNameWords() const + -> const base::flat_set & = 0; + [[nodiscard]] virtual auto chatListFirstLetters() const + -> const base::flat_set & = 0; - virtual bool folderKnown() const { + [[nodiscard]] virtual bool folderKnown() const { return true; } - virtual Data::Folder *folder() const { + [[nodiscard]] virtual Data::Folder *folder() const { return nullptr; } @@ -255,8 +261,9 @@ private: enum class Flag : uchar { IsThread = (1 << 0), IsHistory = (1 << 1), - UpdatePostponed = (1 << 2), - InUnreadChangeBlock = (1 << 3), + IsSavedSublist = (1 << 2), + UpdatePostponed = (1 << 3), + InUnreadChangeBlock = (1 << 4), }; friend inline constexpr bool is_flag_type(Flag) { return true; } using Flags = base::flags; @@ -265,8 +272,6 @@ private: void pinnedIndexChanged(FilterId filterId, int was, int now); [[nodiscard]] uint64 computeSortPosition(FilterId filterId) const; - [[nodiscard]] virtual int chatListNameVersion() const = 0; - void setChatListExistence(bool exists); not_null mainChatListLink(FilterId filterId) const; Row *maybeMainChatListLink(FilterId filterId) const; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 5a9e98a69..c1a630b78 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat_filters.h" #include "data/data_cloud_file.h" #include "data/data_changes.h" +#include "data/data_saved_messages.h" #include "data/data_stories.h" #include "data/stickers/data_stickers.h" #include "data/data_send_action.h" @@ -219,7 +220,9 @@ InnerWidget::InnerWidget( session().data().chatsListChanges(), session().data().chatsListLoadedEvents() ) | rpl::filter([=](Data::Folder *folder) { - return !_openedForum && (folder == _openedFolder); + return !_savedSublists + && !_openedForum + && (folder == _openedFolder); }) | rpl::start_with_next([=] { refresh(); }, lifetime()); @@ -499,6 +502,8 @@ int InnerWidget::searchInChatSkip() const { } void InnerWidget::changeOpenedFolder(Data::Folder *folder) { + Expects(!folder || !_savedSublists); + if (_openedFolder == folder) { return; } @@ -513,6 +518,8 @@ void InnerWidget::changeOpenedFolder(Data::Folder *folder) { } void InnerWidget::changeOpenedForum(Data::Forum *forum) { + Expects(!forum || !_savedSublists); + if (_openedForum == forum) { return; } @@ -553,12 +560,39 @@ void InnerWidget::changeOpenedForum(Data::Forum *forum) { } } +void InnerWidget::showSavedSublists() { + Expects(!_geometryInited); + Expects(!_savedSublists); + + _savedSublists = true; + + stopReorderPinned(); + clearSelection(); + + _filterId = 0; + _openedForum = nullptr; + _st = &st::defaultDialogRow; + refreshShownList(); + + _openedForumLifetime.destroy(); + + //session().data().savedMessages().chatsListChanges( + //) | rpl::start_with_next([=] { + // refresh(); + //}, lifetime()); + + refreshWithCollapsedRows(true); + if (_loadMoreCallback) { + _loadMoreCallback(); + } +} + void InnerWidget::paintEvent(QPaintEvent *e) { Painter p(this); p.setInactive( _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Any)); - if (_controller->contentOverlapped(this, e)) { + if (!_savedSublists && _controller->contentOverlapped(this, e)) { return; } const auto activeEntry = _controller->activeChatEntryCurrent(); @@ -1416,11 +1450,14 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { } } const std::vector &InnerWidget::pinnedChatsOrder() const { - return _openedForum - ? session().data().pinnedChatsOrder(_openedForum) + const auto owner = &session().data(); + return _savedSublists + ? owner->pinnedChatsOrder(&owner->savedMessages()) + : _openedForum + ? owner->pinnedChatsOrder(_openedForum) : _filterId - ? session().data().pinnedChatsOrder(_filterId) - : session().data().pinnedChatsOrder(_openedFolder); + ? owner->pinnedChatsOrder(_filterId) + : owner->pinnedChatsOrder(_openedFolder); } void InnerWidget::checkReorderPinnedStart(QPoint localPosition) { @@ -1473,7 +1510,9 @@ void InnerWidget::savePinnedOrder() { return; // Something has changed in the set of pinned chats. } } - if (_openedForum) { + if (_savedSublists) { + session().api().savePinnedOrder(&session().data().savedMessages()); + } else if (_openedForum) { session().api().savePinnedOrder(_openedForum); } else if (_filterId) { Api::SaveNewFilterPinned(&session(), _filterId); @@ -1577,7 +1616,7 @@ bool InnerWidget::updateReorderPinned(QPoint localPosition) { const auto delta = [&] { if (localPosition.y() < _visibleTop) { return localPosition.y() - _visibleTop; - } else if ((_openedFolder || _openedForum || _filterId) + } else if ((_savedSublists || _openedFolder || _openedForum || _filterId) && localPosition.y() > _visibleBottom) { return localPosition.y() - _visibleBottom; } @@ -1832,6 +1871,8 @@ void InnerWidget::handleChatListEntryRefreshes() { return false; } else if (const auto topic = event.key.topic()) { return (topic->forum() == _openedForum); + } else if (event.key.sublist()) { + return _savedSublists; } else { return !_openedForum; } @@ -1848,6 +1889,8 @@ void InnerWidget::handleChatListEntryRefreshes() { && (_state == WidgetState::Default) && (key.topic() ? (key.topic()->forum() == _openedForum) + : key.sublist() + ? _savedSublists : (entry->folder() == _openedFolder))) { _dialogMoved.fire({ from, to }); } @@ -2051,7 +2094,11 @@ void InnerWidget::enterEventHook(QEnterEvent *e) { Row *InnerWidget::shownRowByKey(Key key) { const auto entry = key.entry(); - if (_openedForum) { + if (_savedSublists) { + if (!entry->asSublist()) { + return nullptr; + } + } else if (_openedForum) { const auto topic = entry->asTopic(); if (!topic || topic->forum() != _openedForum) { return nullptr; @@ -2114,7 +2161,9 @@ void InnerWidget::updateSelectedRow(Key key) { } void InnerWidget::refreshShownList() { - const auto list = _openedForum + const auto list = _savedSublists + ? session().data().savedMessages().chatsList()->indexed() + : _openedForum ? _openedForum->topicsList()->indexed() : _filterId ? session().data().chatsFilters().chatsList(_filterId)->indexed() @@ -2294,15 +2343,19 @@ void InnerWidget::applyFilterUpdate(QString newFilter, bool force) { } }; if (!_searchInChat && !_searchFromPeer && !words.isEmpty()) { - if (_openedForum) { + if (_savedSublists) { + const auto owner = &session().data(); + append(owner->savedMessages().chatsList()->indexed()); + } else if (_openedForum) { append(_openedForum->topicsList()->indexed()); } else { - append(session().data().chatsList()->indexed()); + const auto owner = &session().data(); + append(owner->chatsList()->indexed()); const auto id = Data::Folder::kId; - if (const auto add = session().data().folderLoaded(id)) { + if (const auto add = owner->folderLoaded(id)) { append(add->chatsList()->indexed()); } - append(session().data().contactsNoChatsList()); + append(owner->contactsNoChatsList()); } } refresh(true); @@ -2759,6 +2812,10 @@ void InnerWidget::refreshEmptyLabel() { const auto data = &session().data(); const auto state = !_shownList->empty() ? EmptyState::None + : _savedSublists + ? (data->savedMessages().chatsList()->loaded() + ? EmptyState::EmptySavedSublists + : EmptyState::Loading) : _openedForum ? (_openedForum->topicsList()->loaded() ? EmptyState::EmptyForum @@ -2783,6 +2840,8 @@ void InnerWidget::refreshEmptyLabel() { ? tr::lng_no_chats_filter() : (state == EmptyState::EmptyForum) ? tr::lng_forum_no_topics() + : (state == EmptyState::EmptySavedSublists) + ? tr::lng_no_saved_sublists() : tr::lng_contacts_loading(); auto link = (state == EmptyState::NoContacts) ? tr::lng_add_contact_button() diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 608513c85..55d257bb5 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -107,6 +107,7 @@ public: void changeOpenedFolder(Data::Folder *folder); void changeOpenedForum(Data::Forum *forum); + void showSavedSublists(); void selectSkip(int32 direction); void selectSkipPage(int32 pixels, int32 direction); @@ -198,6 +199,7 @@ private: NoContacts, EmptyFolder, EmptyForum, + EmptySavedSublists, }; struct PinnedRow { @@ -503,6 +505,8 @@ private: float64 _narrowRatio = 0.; bool _geometryInited = false; + bool _savedSublists = false; + base::unique_qptr _menu; }; diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.cpp b/Telegram/SourceFiles/dialogs/dialogs_key.cpp index fe96a5a7d..dfa728141 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_key.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_folder.h" #include "data/data_forum_topic.h" +#include "data/data_saved_sublist.h" #include "history/history.h" namespace Dialogs { @@ -25,6 +26,9 @@ Key::Key(Data::Thread *thread) : _value(thread) { Key::Key(Data::ForumTopic *topic) : _value(topic) { } +Key::Key(Data::SavedSublist *sublist) : _value(sublist) { +} + Key::Key(not_null history) : _value(history) { } @@ -37,6 +41,9 @@ Key::Key(not_null folder) : _value(folder) { Key::Key(not_null topic) : _value(topic) { } +Key::Key(not_null sublist) : _value(sublist) { +} + not_null Key::entry() const { Expects(_value != nullptr); @@ -59,6 +66,10 @@ Data::Thread *Key::thread() const { return _value ? _value->asThread() : nullptr; } +Data::SavedSublist *Key::sublist() const { + return _value ? _value->asSublist() : nullptr; +} + History *Key::owningHistory() const { if (const auto thread = this->thread()) { return thread->owningHistory(); diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h index 34fd9aa29..2396b216f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.h +++ b/Telegram/SourceFiles/dialogs/dialogs_key.h @@ -14,6 +14,7 @@ namespace Data { class Thread; class Folder; class ForumTopic; +class SavedSublist; } // namespace Data namespace Dialogs { @@ -29,12 +30,14 @@ public: Key(Data::Folder *folder); Key(Data::Thread *thread); Key(Data::ForumTopic *topic); + Key(Data::SavedSublist *sublist); Key(not_null entry) : _value(entry) { } Key(not_null history); Key(not_null thread); Key(not_null folder); Key(not_null topic); + Key(not_null sublist); explicit operator bool() const { return (_value != nullptr); @@ -46,6 +49,7 @@ public: [[nodiscard]] Data::Thread *thread() const; [[nodiscard]] History *owningHistory() const; [[nodiscard]] PeerData *peer() const; + [[nodiscard]] Data::SavedSublist *sublist() const; friend inline constexpr auto operator<=>(Key, Key) noexcept = default; @@ -102,6 +106,7 @@ struct EntryState { Scheduled, Pinned, Replies, + SavedSublist, ContextMenu, }; diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp index aaa03035f..54247193c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -261,8 +261,10 @@ void Row::recountHeight(float64 narrowRatio) { : st::defaultDialogRow.height; } else if (_id.folder()) { _height = st::defaultDialogRow.height; - } else { + } else if (_id.topic()) { _height = st::forumTopicRow.height; + } else { + _height = st::defaultDialogRow.height; } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.h b/Telegram/SourceFiles/dialogs/dialogs_row.h index e814e5ff1..738c9faa4 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.h +++ b/Telegram/SourceFiles/dialogs/dialogs_row.h @@ -131,6 +131,9 @@ public: [[nodiscard]] Data::Thread *thread() const { return _id.thread(); } + [[nodiscard]] Data::SavedSublist *sublist() const { + return _id.sublist(); + } [[nodiscard]] not_null entry() const { return _id.entry(); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 85a50038c..30c7ddd88 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1774,6 +1774,7 @@ bool Widget::searchMessages(bool searchCache) { (_searchQueryFrom ? _searchQueryFrom->input : MTP_inputPeerEmpty()), + MTPInputPeer(), // saved_peer_id MTP_int(topic ? topic->rootId() : 0), MTP_inputMessagesFilterEmpty(), MTP_int(0), // min_date @@ -2020,6 +2021,7 @@ void Widget::searchMore() { (_searchQueryFrom ? _searchQueryFrom->input : MTP_inputPeerEmpty()), + MTPInputPeer(), // saved_peer_id MTP_int(topic ? topic->rootId() : 0), MTP_inputMessagesFilterEmpty(), MTP_int(0), // min_date @@ -2092,6 +2094,7 @@ void Widget::searchMore() { (_searchQueryFrom ? _searchQueryFrom->input : MTP_inputPeerEmpty()), + MTPInputPeer(), // saved_peer_id MTPint(), // top_msg_id MTP_inputMessagesFilterEmpty(), MTP_int(0), // min_date @@ -2708,7 +2711,7 @@ void Widget::filterCursorMoved() { } void Widget::completeHashtag(QString tag) { - const auto t = _filter->getLastText();; + const auto t = _filter->getLastText(); auto cur = _filter->textCursor().position(); auto hashtag = QString(); for (int start = cur; start > 0;) { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 5299b648c..91362c73e 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_drafts.h" #include "data/data_forum_topic.h" +#include "data/data_saved_sublist.h" #include "data/data_session.h" #include "dialogs/dialogs_list.h" #include "dialogs/dialogs_three_state_icon.h" @@ -265,10 +266,12 @@ void PaintFolderEntryText( } enum class Flag { - SavedMessages = 0x08, - RepliesMessages = 0x10, - AllowUserOnline = 0x20, - TopicJumpRipple = 0x40, + SavedMessages = 0x008, + RepliesMessages = 0x010, + AllowUserOnline = 0x020, + TopicJumpRipple = 0x040, + HiddenAuthor = 0x080, + MyNotes = 0x100, }; inline constexpr bool is_flag_type(Flag) { return true; } @@ -311,6 +314,7 @@ void PaintRow( const auto history = entry->asHistory(); const auto thread = entry->asThread(); + const auto sublist = entry->asSublist(); if (flags & Flag::SavedMessages) { EmptyUserpic::PaintSavedMessages( @@ -326,6 +330,20 @@ void PaintRow( context.st->padding.top(), context.width, context.st->photoSize); + } else if (flags & Flag::HiddenAuthor) { + EmptyUserpic::PaintHiddenAuthor( + p, + context.st->padding.left(), + context.st->padding.top(), + context.width, + context.st->photoSize); + } else if (flags & Flag::MyNotes) { + EmptyUserpic::PaintMyNotes( + p, + context.st->padding.left(), + context.st->padding.top(), + context.width, + context.st->photoSize); } else if (!from && hiddenSenderInfo) { hiddenSenderInfo->emptyUserpic.paintCircle( p, @@ -547,7 +565,7 @@ void PaintRow( // Empty history } } else if (!item->isEmpty()) { - if (thread && !promoted) { + if ((thread || sublist) && !promoted) { PaintRowDate(p, date, rectForName, context); } @@ -606,10 +624,18 @@ void PaintRow( } p.setFont(st::semiboldFont); - if (flags & (Flag::SavedMessages | Flag::RepliesMessages)) { + if (flags + & (Flag::SavedMessages + | Flag::RepliesMessages + | Flag::HiddenAuthor + | Flag::MyNotes)) { auto text = (flags & Flag::SavedMessages) ? tr::lng_saved_messages(tr::now) - : tr::lng_replies_messages(tr::now); + : (flags & Flag::RepliesMessages) + ? tr::lng_replies_messages(tr::now) + : (flags & Flag::MyNotes) + ? tr::lng_my_notes(tr::now) + : tr::lng_hidden_author_messages(tr::now); const auto textWidth = st::semiboldFont->width(text); if (textWidth > rectForName.width()) { text = st::semiboldFont->elided(text, rectForName.width()); @@ -621,7 +647,7 @@ void PaintRow( : st::dialogsNameFg); p.drawTextLeft(rectForName.left(), rectForName.top(), context.width, text); } else if (from) { - if (history && !context.search) { + if ((history || sublist) && !context.search) { const auto badgeWidth = fromBadge.drawGetWidth( p, rectForName, @@ -732,6 +758,7 @@ void RowPainter::Paint( const auto entry = row->entry(); const auto history = row->history(); const auto thread = row->thread(); + const auto sublist = row->sublist(); const auto peer = history ? history->peer.get() : nullptr; const auto badgesState = entry->chatListBadgesState(); entry->chatListPreloadData(); // Allow chat list message resolve. @@ -771,11 +798,22 @@ void RowPainter::Paint( ? (history->peer->migrateTo() ? history->peer->migrateTo() : history->peer.get()) + : sublist + ? sublist->peer().get() : nullptr; const auto allowUserOnline = true;// !context.narrow || badgesState.empty(); const auto flags = (allowUserOnline ? Flag::AllowUserOnline : Flag(0)) - | (peer && peer->isSelf() ? Flag::SavedMessages : Flag(0)) - | (peer && peer->isRepliesChat() ? Flag::RepliesMessages : Flag(0)) + | ((sublist && from->isSelf()) + ? Flag::MyNotes + : (peer && peer->isSelf()) + ? Flag::SavedMessages + : Flag(0)) + | ((from && from->isRepliesChat()) + ? Flag::RepliesMessages + : Flag(0)) + | ((sublist && from->isSavedHiddenAuthor()) + ? Flag::HiddenAuthor + : Flag(0)) | (row->topicJumpRipple() ? Flag::TopicJumpRipple : Flag(0)); const auto paintItemCallback = [&](int nameleft, int namewidth) { const auto texttop = context.st->textTop; @@ -810,6 +848,8 @@ void RowPainter::Paint( ? nullptr : thread ? &thread->lastItemDialogsView() + : sublist + ? &sublist->lastItemDialogsView() : nullptr; if (view) { const auto forum = context.st->topicsHeight @@ -872,7 +912,9 @@ void RowPainter::Paint( if (const auto peer = searchChat.peer()) { if (const auto forwarded = item->Get()) { if (peer->isSelf() || forwarded->imported) { - return forwarded->hiddenSenderInfo.get(); + return forwarded->savedFromHiddenSenderInfo.get() + ? forwarded->savedFromHiddenSenderInfo.get() + : forwarded->originalHiddenSenderInfo.get(); } } } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp index cda8da94d..e6a93af3a 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp @@ -36,7 +36,6 @@ constexpr auto kCollapseAfterRatio = 0.68; constexpr auto kFrictionRatio = 0.15; constexpr auto kExpandCatchUpDuration = crl::time(200); constexpr auto kMaxTooltipNames = 3; -constexpr auto kStoriesTooltipHideBgOpacity = 0.2; [[nodiscard]] int AvailableNameWidth(const style::DialogsStoriesList &st) { const auto &full = st.full; diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index 5ed8addd9..61f29b16c 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -1624,6 +1624,7 @@ void ApiWrap::requestChatMessages( realPeerInput, MTP_string(), // query MTP_inputPeerSelf(), + MTPInputPeer(), // saved_peer_id MTPint(), // top_msg_id MTP_inputMessagesFilterEmpty(), MTP_int(0), // min_date diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index 9a5f5b3d6..dc23fd38f 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -446,7 +446,6 @@ QByteArray SerializeMessage( pushAction("send_payment"); push("amount", data.amount); push("currency", data.currency); - const auto amount = FormatMoneyAmount(data.amount, data.currency); pushReplyToMsgId("invoice_message_id"); if (data.recurringUsed) { push("recurring", "used"); diff --git a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp index b59bf86ba..378d9b4d1 100644 --- a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp +++ b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp @@ -134,8 +134,9 @@ void PremultiplyLine(uchar *dst, const uchar *src, int intsCount) { } DEBUG_LOG(("Video Info: " "Trying \"%1\" hardware acceleration for \"%2\" decoder." - ).arg(av_hwdevice_get_type_name(type) - ).arg(context->codec->name)); + ).arg( + av_hwdevice_get_type_name(type), + context->codec->name)); if (parent->hw_device_ctx) { av_buffer_unref(&parent->hw_device_ctx); } diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index 84be6934d..fb6f950e0 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -442,7 +442,6 @@ void InnerWidget::applyFilter(FilterValue &&value) { } void InnerWidget::applySearch(const QString &query) { - auto clearQuery = query.trimmed(); if (_searchQuery != query) { _searchQuery = query; clearAndRequestLog(); diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index 6f4f61be5..f9ece93b0 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -117,6 +117,7 @@ MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) { data.vid(), data.vfrom_id() ? *data.vfrom_id() : MTPPeer(), data.vpeer_id(), + MTPPeer(), // saved_peer_id data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(), MTP_long(data.vvia_bot_id().value_or_empty()), MTPMessageReplyHeader(), diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 3e998600b..629fae9c9 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/notify/data_notify_settings.h" #include "data/stickers/data_stickers.h" #include "data/data_drafts.h" +#include "data/data_saved_sublist.h" #include "data/data_session.h" #include "data/data_media_types.h" #include "data/data_channel_admins.h" @@ -614,6 +615,11 @@ not_null History::addNewItem( addNewToBack(item, unread); checkForLoadedAtTop(item); } + + if (const auto sublist = item->savedSublist()) { + sublist->applyMaybeLast(item, unread); + } + return item; } @@ -1436,6 +1442,12 @@ void History::addCreatedOlderSlice( if (loadedAtBottom()) { // Add photos to overview and authors to lastAuthors. addItemsToLists(items); + + for (const auto &item : items) { + if (const auto sublist = item->savedSublist()) { + sublist->applyMaybeLast(item); + } + } } addToSharedMedia(items); } diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 909549684..adeb63fc2 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -365,6 +365,7 @@ public: void takeLocalDraft(not_null from); void applyCloudDraft(MsgId topicRootId); void draftSavedToCloud(MsgId topicRootId); + void requestChatListMessage(); [[nodiscard]] const Data::ForwardDraft &forwardDraft( MsgId topicRootId) const; @@ -383,9 +384,9 @@ public: Dialogs::BadgesState chatListBadgesState() const override; HistoryItem *chatListMessage() const override; bool chatListMessageKnown() const override; - void requestChatListMessage() override; const QString &chatListName() const override; const QString &chatListNameSortKey() const override; + int chatListNameVersion() const override; const base::flat_set &chatListNameWords() const override; const base::flat_set &chatListFirstLetters() const override; void chatListPreloadData() override; @@ -589,8 +590,6 @@ private: [[nodiscard]] Dialogs::UnreadState computeUnreadState() const; void setFolderPointer(Data::Folder *folder); - int chatListNameVersion() const override; - void hasUnreadMentionChanged(bool has) override; void hasUnreadReactionChanged(bool has) override; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index d78aec6ac..7de4a765f 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -1223,7 +1223,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { width(), st::msgPhotoSize, context.paused); - } else if (const auto info = item->hiddenSenderInfo()) { + } else if (const auto info = item->displayHiddenSenderInfo()) { if (info->customUserpic.empty()) { info->emptyUserpic.paintCircle( p, diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 80733f204..43fe722a0 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -41,6 +41,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_updates.h" #include "data/notify/data_notify_settings.h" #include "data/data_bot_app.h" +#include "data/data_saved_messages.h" +#include "data/data_saved_sublist.h" #include "data/data_scheduled_messages.h" #include "data/data_changes.h" #include "data/data_session.h" @@ -150,10 +152,16 @@ struct HistoryItem::CreateConfig { QString originalSenderName; QString originalPostAuthor; + PeerId savedSublistPeer = 0; + QString forwardPsaType; PeerId savedFromPeer = 0; MsgId savedFromMsgId = 0; + PeerId savedFromSenderId = 0; + QString savedFromSenderName; + bool savedFromOutgoing = false; + TimeId editDate = 0; HistoryMessageMarkupData markup; HistoryMessageRepliesData replies; @@ -180,6 +188,13 @@ void HistoryItem::FillForwardedInfo( config.savedFromPeer = peerFromMTP(*savedFromPeer); config.savedFromMsgId = savedFromMsgId->v; } + config.savedFromSenderId = data.vsaved_from_id() + ? peerFromMTP(*data.vsaved_from_id()) + : PeerId(); + config.savedFromSenderName = qs( + data.vsaved_from_name().value_or_empty()); + config.savedFromOutgoing = data.is_saved_out(); + config.imported = data.is_imported(); } @@ -259,7 +274,8 @@ std::unique_ptr HistoryItem::CreateMedia( item, item->history()->owner().processDocument(document), media.is_nopremium(), - media.is_spoiler()); + media.is_spoiler(), + media.vttl_seconds().value_or_empty()); }, [](const MTPDdocumentEmpty &) -> Result { return nullptr; }); @@ -491,7 +507,7 @@ HistoryItem::HistoryItem( } if (!dropForwardInfo) { config.originalDate = original->originalDate(); - if (const auto info = original->hiddenSenderInfo()) { + if (const auto info = original->originalHiddenSenderInfo()) { config.originalSenderName = info->name; } else if (const auto originalSender = original->originalSender()) { config.originalSenderId = originalSender->id; @@ -515,6 +531,11 @@ HistoryItem::HistoryItem( config.savedFromPeer = original->history()->peer->id; config.savedFromMsgId = original->id; //} + + config.savedFromOutgoing = original->out(); + config.savedFromSenderId = original->Get() + ? original->author()->id + : PeerId(); } if (flags & MessageFlag::HasPostAuthor) { config.postAuthor = postAuthor; @@ -634,7 +655,8 @@ HistoryItem::HistoryItem( this, document, skipPremiumEffect, - spoiler); + spoiler, + /*ttlSeconds = */0); setText(caption); } @@ -790,6 +812,9 @@ HistoryItem::~HistoryItem() { if (const auto reply = Get()) { reply->clearData(this); } + if (const auto saved = Get()) { + saved->sublist->removeOne(this); + } clearDependencyMessage(); applyTTL(0); } @@ -1214,10 +1239,10 @@ PeerData *HistoryItem::computeDisplayFrom() const { if (const auto sender = discussionPostOriginalSender()) { return sender; } else if (const auto forwarded = Get()) { - if (_history->peer->isSelf() - || _history->peer->isRepliesChat() - || forwarded->imported) { - return forwarded->originalSender; + if (showForwardsFromSender(forwarded)) { + return forwarded->forwardOfForward() + ? forwarded->savedFromSender + : forwarded->originalSender; } } return author().get(); @@ -1234,10 +1259,10 @@ PeerData *HistoryItem::displayFrom() const { uint8 HistoryItem::colorIndex() const { if (const auto from = displayFrom()) { return from->colorIndex(); - } else if (const auto info = hiddenSenderInfo()) { + } else if (const auto info = displayHiddenSenderInfo()) { return info->colorIndex; } - Unexpected("No displayFrom and no hiddenSenderInfo."); + Unexpected("No displayFrom and no displayHiddenSenderInfo."); } std::unique_ptr HistoryItem::createView( @@ -1260,6 +1285,9 @@ void HistoryItem::invalidateChatListEntry() { if (const auto topic = this->topic()) { topic->lastItemDialogsView().itemInvalidated(this); } + if (const auto sublist = savedSublist()) { + sublist->lastItemDialogsView().itemInvalidated(this); + } } void HistoryItem::customEmojiRepaint() { @@ -1680,7 +1708,8 @@ void HistoryItem::setStoryFields(not_null story) { this, document, /*skipPremiumEffect=*/false, - spoiler); + spoiler, + /*ttlSeconds = */0); } setText(story->caption()); } @@ -2510,16 +2539,32 @@ PeerData *HistoryItem::originalSender() const { return forwarded->originalSender; } const auto peer = _history->peer; - return (peer->isChannel() && !peer->isMegagroup()) ? peer : from(); + return peer->isBroadcast() ? peer : from(); } -const HiddenSenderInfo *HistoryItem::hiddenSenderInfo() const { +const HiddenSenderInfo *HistoryItem::originalHiddenSenderInfo() const { if (const auto forwarded = Get()) { - return forwarded->hiddenSenderInfo.get(); + return forwarded->originalHiddenSenderInfo.get(); } return nullptr; } +const HiddenSenderInfo *HistoryItem::displayHiddenSenderInfo() const { + if (const auto forwarded = Get()) { + return forwarded->savedFromHiddenSenderInfo + ? forwarded->savedFromHiddenSenderInfo.get() + : forwarded->originalHiddenSenderInfo.get(); + } + return nullptr; +} + +bool HistoryItem::showForwardsFromSender( + not_null forwarded) const { + const auto peer = history()->peer; + return !forwarded->story + && (peer->isSelf() || peer->isRepliesChat() || forwarded->imported); +} + not_null HistoryItem::fromOriginal() const { if (const auto forwarded = Get()) { if (forwarded->originalSender) { @@ -3104,6 +3149,34 @@ bool HistoryItem::isEmpty() const { && !Has(); } +Data::SavedSublist *HistoryItem::savedSublist() const { + if (const auto saved = Get()) { + return saved->sublist; + } + return nullptr; +} + +PeerData *HistoryItem::savedSublistPeer() const { + if (const auto sublist = savedSublist()) { + return sublist->peer(); + } + return nullptr; +} + +PeerData *HistoryItem::savedFromSender() const { + if (const auto forwarded = Get()) { + return forwarded->savedFromSender; + } + return nullptr; +} + +const HiddenSenderInfo *HistoryItem::savedFromHiddenSenderInfo() const { + if (const auto forwarded = Get()) { + return forwarded->savedFromHiddenSenderInfo.get(); + } + return nullptr; +} + TextWithEntities HistoryItem::notificationText( NotificationTextOptions options) const { auto result = [&] { @@ -3156,16 +3229,25 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const { ? tr::lng_from_you(tr::now) : sender->shortName(); }; - result.icon = (Get() != nullptr) + const auto forwarded = Get(); + const auto forwardFromSender = forwarded + && showForwardsFromSender(forwarded); + result.icon = (forwarded + && (!forwardFromSender || forwarded->forwardOfForward())) ? ItemPreview::Icon::ForwardedMessage : replyToStory().valid() ? ItemPreview::Icon::ReplyToStory : ItemPreview::Icon::None; const auto fromForwarded = [&]() -> std::optional { - if (const auto forwarded = Get()) { - return forwarded->originalSender - ? fromSender(forwarded->originalSender) - : forwarded->hiddenSenderInfo->name; + if (forwarded) { + const auto sender = forwarded->forwardOfForward() + ? forwarded->savedFromSender + : forwarded->originalSender; + return sender + ? fromSender(sender) + : forwarded->savedFromHiddenSenderInfo + ? forwarded->savedFromHiddenSenderInfo->name + : forwarded->originalHiddenSenderInfo->name; } return {}; }; @@ -3255,9 +3337,28 @@ void HistoryItem::createComponents(CreateConfig &&config) { } else if (config.inlineMarkup) { mask |= HistoryMessageReplyMarkup::Bit(); } + if (_history->peer->isSelf()) { + mask |= HistoryMessageSaved::Bit(); + } UpdateComponents(mask); + if (const auto saved = Get()) { + if (!config.savedSublistPeer) { + if (config.savedFromPeer) { + config.savedSublistPeer = config.savedFromPeer; + } else if (config.originalSenderId) { + config.savedSublistPeer = config.originalSenderId; + } else if (!config.originalSenderName.isEmpty()) { + config.savedSublistPeer = PeerData::kSavedHiddenAuthorId; + } else { + config.savedSublistPeer = _history->session().userPeerId(); + } + } + const auto peer = _history->owner().peer(config.savedSublistPeer); + saved->sublist = _history->owner().savedMessages().sublist(peer); + } + if (const auto reply = Get()) { reply->set(std::move(config.reply)); if (!reply->updateData(this)) { @@ -3343,9 +3444,10 @@ void HistoryItem::setupForwardedComponent(const CreateConfig &config) { ? _history->owner().peer(originalSender).get() : nullptr; if (!forwarded->originalSender) { - forwarded->hiddenSenderInfo = std::make_unique( - config.originalSenderName, - config.imported); + forwarded->originalHiddenSenderInfo + = std::make_unique( + config.originalSenderName, + config.imported); } forwarded->originalId = config.originalId; forwarded->originalPostAuthor = config.originalPostAuthor; @@ -3353,6 +3455,14 @@ void HistoryItem::setupForwardedComponent(const CreateConfig &config) { forwarded->savedFromPeer = _history->owner().peerLoaded( config.savedFromPeer); forwarded->savedFromMsgId = config.savedFromMsgId; + forwarded->savedFromSender = _history->owner().peerLoaded( + config.savedFromSenderId); + forwarded->savedFromOutgoing = config.savedFromOutgoing; + if (!forwarded->savedFromSender + && !config.savedFromSenderName.isEmpty()) { + forwarded->savedFromHiddenSenderInfo + = std::make_unique(config.savedFromSenderName, false); + } forwarded->imported = config.imported; } @@ -3551,6 +3661,9 @@ void HistoryItem::applyTTL(const MTPDmessageService &data) { void HistoryItem::createComponents(const MTPDmessage &data) { auto config = CreateConfig(); + config.savedSublistPeer = data.vsaved_peer_id() + ? peerFromMTP(*data.vsaved_peer_id()) + : PeerId(); if (const auto forwarded = data.vfwd_from()) { forwarded->match([&](const MTPDmessageFwdHeader &data) { FillForwardedInfo(config, data); @@ -3636,25 +3749,43 @@ void HistoryItem::createServiceFromMtp(const MTPDmessage &message) { const auto ttl = data.vttl_seconds(); Assert(ttl != nullptr); - setSelfDestruct(HistoryServiceSelfDestruct::Type::Video, *ttl); - if (out()) { - setServiceText({ - tr::lng_ttl_video_sent(tr::now, Ui::Text::WithEntities) - }); - } else { - auto result = PreparedServiceText(); - result.links.push_back(fromLink()); - result.text = tr::lng_ttl_video_received( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); - setServiceText(std::move(result)); + if (data.is_video()) { + setSelfDestruct( + HistoryServiceSelfDestruct::Type::Video, + *ttl); + if (out()) { + setServiceText({ + tr::lng_ttl_video_sent( + tr::now, + Ui::Text::WithEntities) + }); + } else { + auto result = PreparedServiceText(); + result.links.push_back(fromLink()); + result.text = tr::lng_ttl_video_received( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + setServiceText(std::move(result)); + } + } else if (out()) { + auto text = (data.is_voice() + ? tr::lng_ttl_voice_sent + : data.is_round() + ? tr::lng_ttl_round_sent + : tr::lng_message_empty)(tr::now, Ui::Text::WithEntities); + setServiceText({ std::move(text) }); } } else { - setServiceText({ - tr::lng_ttl_video_expired(tr::now, Ui::Text::WithEntities) - }); + auto text = (data.is_video() + ? tr::lng_ttl_video_expired + : data.is_voice() + ? tr::lng_ttl_voice_expired + : data.is_round() + ? tr::lng_ttl_round_expired + : tr::lng_message_empty)(tr::now, Ui::Text::WithEntities); + setServiceText({ std::move(text) }); } }, [&](const MTPDmessageMediaStory &data) { setServiceText(prepareStoryMentionText()); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 413973d5f..a89d5d30d 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -20,6 +20,7 @@ struct HistoryMessageViews; struct HistoryMessageMarkupData; struct HistoryMessageReplyMarkup; struct HistoryMessageTranslation; +struct HistoryMessageForwarded; struct HistoryServiceDependentData; enum class HistorySelfDestructType; struct PreparedServiceText; @@ -56,6 +57,7 @@ class ForumTopic; class Thread; struct SponsoredFrom; class Story; +class SavedSublist; } // namespace Data namespace Main { @@ -481,11 +483,21 @@ public: [[nodiscard]] TimeId originalDate() const; [[nodiscard]] PeerData *originalSender() const; - [[nodiscard]] const HiddenSenderInfo *hiddenSenderInfo() const; + [[nodiscard]] const HiddenSenderInfo *originalHiddenSenderInfo() const; [[nodiscard]] not_null fromOriginal() const; [[nodiscard]] QString originalPostAuthor() const; [[nodiscard]] MsgId originalId() const; + [[nodiscard]] Data::SavedSublist *savedSublist() const; + [[nodiscard]] PeerData *savedSublistPeer() const; + [[nodiscard]] PeerData *savedFromSender() const; + [[nodiscard]] const HiddenSenderInfo *savedFromHiddenSenderInfo() const; + + [[nodiscard]] const HiddenSenderInfo *displayHiddenSenderInfo() const; + + [[nodiscard]] bool showForwardsFromSender( + not_null forwarded) const; + [[nodiscard]] bool isEmpty() const; [[nodiscard]] MessageGroupId groupId() const; diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 4d93f95e7..82a861c81 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -190,7 +190,7 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { const auto name = TextWithEntities{ .text = (originalSender ? originalSender->name() - : hiddenSenderInfo->name) + : originalHiddenSenderInfo->name) }; if (!originalPostAuthor.isEmpty()) { phrase = tr::lng_forwarded_signed( diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index d2ada01e1..b4a6981bb 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -126,9 +126,13 @@ private: struct HistoryMessageForwarded : public RuntimeComponent { void create(const HistoryMessageVia *via) const; + [[nodiscard]] bool forwardOfForward() const { + return savedFromSender || savedFromHiddenSenderInfo; + } + TimeId originalDate = 0; PeerData *originalSender = nullptr; - std::unique_ptr hiddenSenderInfo; + std::unique_ptr originalHiddenSenderInfo; QString originalPostAuthor; QString psaType; MsgId originalId = 0; @@ -136,10 +140,19 @@ struct HistoryMessageForwarded : public RuntimeComponent savedFromHiddenSenderInfo; + + bool savedFromOutgoing = false; bool imported = false; bool story = false; }; +struct HistoryMessageSaved : public RuntimeComponent { + Data::SavedSublist *sublist = nullptr; +}; + class ReplyToMessagePointer final { public: ReplyToMessagePointer(HistoryItem *item = nullptr) : _data(item) { diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 0c59ed76f..989c8c5ee 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_changes.h" +#include "data/data_document.h" #include "data/data_group_call.h" #include "data/data_forum.h" #include "data/data_forum_topic.h" @@ -452,7 +453,7 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { }, [](const MTPDmessageMediaPhoto &data) { const auto photo = data.vphoto(); if (data.vttl_seconds()) { - return Result::HasTimeToLive; + return Result::HasUnsupportedTimeToLive; } else if (!photo) { return Result::Empty; } @@ -464,7 +465,11 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { }, [](const MTPDmessageMediaDocument &data) { const auto document = data.vdocument(); if (data.vttl_seconds()) { - return Result::HasTimeToLive; + if (data.is_video()) { + return Result::HasUnsupportedTimeToLive; + } else if (!document) { + return Result::HasExpiredMediaTimeToLive; + } } else if (!document) { return Result::Empty; } @@ -780,3 +785,31 @@ void ShowTrialTranscribesToast(int left, TimeId until) { .filter = filter, }); } + +void ClearMediaAsExpired(not_null item) { + if (const auto media = item->media()) { + if (!media->ttlSeconds()) { + return; + } + if (const auto document = media->document()) { + item->applyEditionToHistoryCleared(); + auto text = (document->isVideoFile() + ? tr::lng_ttl_video_expired + : document->isVoiceMessage() + ? tr::lng_ttl_voice_expired + : document->isVideoMessage() + ? tr::lng_ttl_round_expired + : tr::lng_message_empty)(tr::now, Ui::Text::WithEntities); + item->updateServiceText(PreparedServiceText{ std::move(text) }); + } else if (const auto photo = media->photo()) { + item->applyEditionToHistoryCleared(); + item->updateServiceText(PreparedServiceText{ + tr::lng_ttl_photo_expired(tr::now, Ui::Text::WithEntities) + }); + } + } +} + +[[nodiscard]] bool IsVoiceOncePlayable(not_null item) { + return !item->out() && item->media()->ttlSeconds(); +} diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index 661efe652..f96d9c092 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -43,7 +43,8 @@ enum class MediaCheckResult { Good, Unsupported, Empty, - HasTimeToLive, + HasExpiredMediaTimeToLive, + HasUnsupportedTimeToLive, HasStoryMention, }; [[nodiscard]] MediaCheckResult CheckMessageMedia( @@ -155,3 +156,6 @@ ClickHandlerPtr JumpToStoryClickHandler( CallId callId); void ShowTrialTranscribesToast(int left, TimeId until); + +void ClearMediaAsExpired(not_null item); +[[nodiscard]] bool IsVoiceOncePlayable(not_null item); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp index 0786b0745..d1dbbd0e3 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp @@ -281,7 +281,7 @@ private: base::unique_qptr _cancel; base::unique_qptr _select; - rpl::variable _from = nullptr;; + rpl::variable _from = nullptr; base::Timer _searchTimer; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp index 9c7ce70c5..4eef16970 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -376,7 +376,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { userpicTop, width(), st::msgPhotoSize); - } else if (const auto info = item->hiddenSenderInfo()) { + } else if (const auto info = item->originalHiddenSenderInfo()) { if (info->customUserpic.empty()) { info->emptyUserpic.paintCircle( p, diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp index ff3347cf5..868e55383 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -127,7 +127,7 @@ void ForwardPanel::checkTexts() { for (const auto item : _data.items) { if (const auto from = item->originalSender()) { version += from->nameVersion(); - } else if (const auto info = item->hiddenSenderInfo()) { + } else if (const auto info = item->originalHiddenSenderInfo()) { ++version; } else { Unexpected("Corrupt forwarded information in message."); @@ -168,7 +168,7 @@ void ForwardPanel::updateTexts() { names.push_back(from->shortName()); fullname = from->name(); } - } else if (const auto info = item->hiddenSenderInfo()) { + } else if (const auto info = item->originalHiddenSenderInfo()) { if (!insertedNames.contains(info->name)) { insertedNames.emplace(info->name); names.push_back(info->firstName); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 771555441..028286ca8 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -88,8 +88,7 @@ enum class FilterType { const auto durationString = Ui::FormatDurationText(duration / kPrecision); const auto decimalPart = duration % kPrecision; return QString("%1%2%3") - .arg(durationString) - .arg(QLocale().decimalPoint()) + .arg(durationString, QLocale().decimalPoint()) .arg(decimalPart); } diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index a599e7f2c..8d787b017 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -118,7 +118,7 @@ void SavePhotoToFile(not_null photo) { return; } - const auto image = media->image(Data::PhotoSize::Large)->original(); + const auto image = media->image(Data::PhotoSize::Large)->original(); // clazy:exclude=unused-non-trivial-variable FileDialog::GetWritePath( Core::App().getFileDialogParent(), tr::lng_save_photo(tr::now), diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 34b7a39e0..4f82e7578 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -64,16 +64,20 @@ Element *MousedElement/* = nullptr*/; HistoryMessageForwarded *prevForwarded, not_null item, HistoryMessageForwarded *forwarded) { - const auto sender = previous->originalSender(); + const auto sender = previous->displayFrom(); if ((prevForwarded != nullptr) != (forwarded != nullptr)) { return false; - } else if (sender != item->originalSender()) { + } else if (sender != item->displayFrom()) { return false; } else if (!prevForwarded || sender) { return true; } - const auto previousInfo = prevForwarded->hiddenSenderInfo.get(); - const auto itemInfo = forwarded->hiddenSenderInfo.get(); + const auto previousInfo = prevForwarded->savedFromHiddenSenderInfo + ? prevForwarded->savedFromHiddenSenderInfo.get() + : prevForwarded->originalHiddenSenderInfo.get(); + const auto itemInfo = forwarded->savedFromHiddenSenderInfo + ? forwarded->savedFromHiddenSenderInfo.get() + : forwarded->originalHiddenSenderInfo.get(); Assert(previousInfo != nullptr); Assert(itemInfo != nullptr); return (*previousInfo == *itemInfo); @@ -1343,6 +1347,10 @@ bool Element::hasOutLayout() const { return false; } +bool Element::hasRightLayout() const { + return hasOutLayout() && !_delegate->elementIsChatWide(); +} + bool Element::drawBubble() const { return false; } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index b0d0a8fc6..3fbaa598f 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -56,7 +56,8 @@ enum class Context : char { Replies, Pinned, AdminLog, - ContactPreview + ContactPreview, + SavedSublist, }; enum class OnlyEmojiAndSpaces : char { @@ -438,6 +439,7 @@ public: [[nodiscard]] virtual TopicButton *displayedTopicButton() const; [[nodiscard]] virtual bool displayForwardedFrom() const; [[nodiscard]] virtual bool hasOutLayout() const; + [[nodiscard]] bool hasRightLayout() const; [[nodiscard]] virtual bool drawBubble() const; [[nodiscard]] virtual bool hasBubble() const; [[nodiscard]] virtual bool unwrapped() const; diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp index aeafdf156..91e18f9d8 100644 --- a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp @@ -220,8 +220,7 @@ QRect EmojiInteractions::computeRect(const Play &play) const { ? int(sticker.width() * kPremiumShift) : (size.width() / 40); const auto inner = view->innerGeometry(); - const auto rightAligned = view->hasOutLayout() - && !view->delegate()->elementIsChatWide(); + const auto rightAligned = view->hasRightLayout(); const auto left = rightAligned ? (inner.x() + inner.width() + shift - size.width()) : (inner.x() - shift); @@ -241,8 +240,7 @@ void EmojiInteractions::paint(QPainter &p) { } auto request = Lottie::FrameRequest(); request.box = play.outer * factor; - const auto rightAligned = play.view->hasOutLayout() - && !play.view->delegate()->elementIsChatWide(); + const auto rightAligned = play.view->hasRightLayout(); if (!rightAligned) { request.mirrorHorizontal = true; } diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 08332097f..50822615c 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -2181,7 +2181,7 @@ void ListWidget::paintEvent(QPaintEvent *e) { userpicTop, view->width(), st::msgPhotoSize); - } else if (const auto info = item->hiddenSenderInfo()) { + } else if (const auto info = item->displayHiddenSenderInfo()) { if (info->customUserpic.empty()) { info->emptyUserpic.paintCircle( p, @@ -3665,7 +3665,7 @@ void ListWidget::performDrag() { _reactionsManager->updateButton({}); _controller->widget()->launchDrag( std::move(mimeData), - crl::guard(this, [=] { mouseActionUpdate(QCursor::pos()); }));; + crl::guard(this, [=] { mouseActionUpdate(QCursor::pos()); })); } } diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 50d066f4c..54e03343c 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -738,7 +738,7 @@ QSize Message::performCountOptimalSize() { validateFromNameText(from); const auto &name = from ? _fromName - : item->hiddenSenderInfo()->nameText(); + : item->displayHiddenSenderInfo()->nameText(); auto namew = st::msgPadding.left() + name.maxWidth() + (_fromNameStatus @@ -1186,7 +1186,9 @@ void Message::draw(Painter &p, const PaintContext &context) const { (g.height() - size->height()) / 2, 0, st::historyFastShareBottom); - const auto fastShareLeft = g.left() + g.width() + st::historyFastShareLeft; + const auto fastShareLeft = hasRightLayout() + ? (g.left() - size->width() - st::historyFastShareLeft) + : (g.left() + g.width() + st::historyFastShareLeft); const auto fastShareTop = data()->isSponsored() ? g.top() + fastShareSkip : g.top() + g.height() - fastShareSkip - size->height(); @@ -1389,7 +1391,7 @@ void Message::paintFromName( const auto stm = context.messageStyle(); const auto from = item->displayFrom(); - const auto info = from ? nullptr : item->hiddenSenderInfo(); + const auto info = from ? nullptr : item->displayHiddenSenderInfo(); Assert(from || info); const auto nameFg = !context.outbg ? FromNameFg(context, colorIndex()) @@ -1974,13 +1976,6 @@ void Message::unloadHeavyPart() { } } -bool Message::showForwardsFromSender( - not_null forwarded) const { - const auto peer = data()->history()->peer; - return !forwarded->story - && (peer->isSelf() || peer->isRepliesChat() || forwarded->imported); -} - bool Message::hasFromPhoto() const { if (isHidden()) { return false; @@ -1990,7 +1985,8 @@ bool Message::hasFromPhoto() const { return true; case Context::History: case Context::Pinned: - case Context::Replies: { + case Context::Replies: + case Context::SavedSublist: { const auto item = data(); if (item->isPost()) { return false; @@ -2003,7 +1999,7 @@ bool Message::hasFromPhoto() const { } else if (const auto forwarded = item->Get()) { const auto peer = item->history()->peer; if (peer->isSelf() || peer->isRepliesChat()) { - return true; + return !hasOutLayout(); } } return !item->out() && !item->history()->peer->isUser(); @@ -2205,7 +2201,9 @@ TextState Message::textState( (g.height() - size->height()) / 2, 0, st::historyFastShareBottom); - const auto fastShareLeft = g.left() + g.width() + st::historyFastShareLeft; + const auto fastShareLeft = hasRightLayout() + ? (g.left() - size->width() - st::historyFastShareLeft) + : (g.left() + g.width() + st::historyFastShareLeft); const auto fastShareTop = data()->isSponsored() ? g.top() + fastShareSkip : g.top() + g.height() - fastShareSkip - size->height(); @@ -2328,7 +2326,7 @@ bool Message::getStateFromName( if (from) { validateFromNameText(from); return &_fromName; - } else if (const auto info = item->hiddenSenderInfo()) { + } else if (const auto info = item->displayHiddenSenderInfo()) { return &info->nameText(); } else { Unexpected("Corrupt forwarded information in message."); @@ -2767,11 +2765,10 @@ Reactions::ButtonParameters Message::reactionButtonParameters( const TextState &reactionState) const { using namespace Reactions; auto result = ButtonParameters{ .context = data()->fullId() }; - const auto outbg = hasOutLayout(); const auto outsideBubble = (!_comments && !embedReactionsInBubble()); const auto geometry = countGeometry(); result.pointer = position; - const auto onTheLeft = (outbg && !delegate()->elementIsChatWide()); + const auto onTheLeft = hasRightLayout(); const auto keyboard = data()->inlineReplyKeyboard(); const auto keyboardHeight = keyboard @@ -3017,7 +3014,9 @@ void Message::validateFromNameText(PeerData *from) const { Ui::NameTextOptions()); } if (from->isPremium() - || (from->isChannel() && from != history()->peer)) { + || (from->isChannel() + && from->emojiStatusId() + && from != history()->peer)) { if (!_fromNameStatus) { _fromNameStatus = std::make_unique(); const auto size = st::emojiSize; @@ -3162,10 +3161,17 @@ bool Message::hasFromName() const { return true; case Context::History: case Context::Pinned: - case Context::Replies: { + case Context::Replies: + case Context::SavedSublist: { const auto item = data(); const auto peer = item->history()->peer; if (hasOutLayout() && !item->from()->isChannel()) { + if (peer->isSelf()) { + if (const auto forwarded = item->Get()) { + return forwarded->savedFromSender + && forwarded->savedFromSender->isChannel(); + } + } return false; } else if (!peer->isUser()) { if (const auto media = this->media()) { @@ -3177,7 +3183,7 @@ bool Message::hasFromName() const { if (forwarded->imported && peer.get() == forwarded->originalSender) { return false; - } else if (showForwardsFromSender(forwarded)) { + } else if (item->showForwardsFromSender(forwarded)) { return true; } } @@ -3201,8 +3207,9 @@ bool Message::displayForwardedFrom() const { if (const auto forwarded = item->Get()) { if (forwarded->story) { return true; - } else if (showForwardsFromSender(forwarded)) { - return false; + } else if (item->showForwardsFromSender(forwarded)) { + return forwarded->savedFromSender + && (forwarded->savedFromSender != forwarded->originalSender); } if (const auto sender = item->discussionPostOriginalSender()) { if (sender == forwarded->originalSender) { @@ -3218,12 +3225,21 @@ bool Message::displayForwardedFrom() const { bool Message::hasOutLayout() const { const auto item = data(); if (item->history()->peer->isSelf()) { - return !item->Has(); + if (const auto forwarded = item->Get()) { + return (context() == Context::SavedSublist) + && (!forwarded->forwardOfForward() + ? (forwarded->originalSender + && forwarded->originalSender->isSelf()) + : ((forwarded->savedFromSender + && forwarded->savedFromSender->isSelf()) + || forwarded->savedFromOutgoing)); + } + return true; } else if (const auto forwarded = item->Get()) { if (!forwarded->imported || !forwarded->originalSender || !forwarded->originalSender->isSelf()) { - if (showForwardsFromSender(forwarded)) { + if (item->showForwardsFromSender(forwarded)) { return false; } } @@ -3323,6 +3339,7 @@ bool Message::displayFastReply() const { bool Message::displayRightActionComments() const { return !isPinnedContext() + && (context() != Context::SavedSublist) && data()->repliesAreComments() && media() && media()->isDisplayed() @@ -3357,11 +3374,10 @@ bool Message::displayFastShare() const { return !peer->isMegagroup(); } else if (const auto user = peer->asUser()) { if (const auto forwarded = item->Get()) { - return !showForwardsFromSender(forwarded) - && !item->out() + return !item->out() && forwarded->originalSender - && forwarded->originalSender->isChannel() - && !forwarded->originalSender->isMegagroup(); + && forwarded->originalSender->isBroadcast() + && !item->showForwardsFromSender(forwarded); } else if (user->isBot() && !item->out()) { if (const auto media = this->media()) { return media->allowsFastShare(); @@ -3380,7 +3396,7 @@ bool Message::displayGoToOriginal() const { return forwarded->savedFromPeer && forwarded->savedFromMsgId && (!item->externalReply() || !hasBubble()) - && !(context() == Context::Replies); + && (context() != Context::Replies); } return false; } @@ -3449,7 +3465,9 @@ void Message::drawRightAction( } else { const auto &icon = data()->isSponsored() ? st->historyFastCloseIcon() - : (displayFastShare() && !isPinnedContext()) + : (displayFastShare() + && !isPinnedContext() + && this->context() != Context::SavedSublist) ? st->historyFastShareIcon() : st->historyGoToOriginalIcon(); icon.paintInCenter(p, { left, top, size->width(), size->height() }); @@ -3481,7 +3499,8 @@ ClickHandlerPtr Message::prepareRightActionLink() const { return HideSponsoredClickHandler(); } else if (isPinnedContext()) { return JumpToMessageClickHandler(data()); - } else if (displayRightActionComments()) { + } else if ((context() != Context::SavedSublist) + && displayRightActionComments()) { return createGoToCommentsLink(); } const auto sessionId = data()->history()->session().uniqueId(); @@ -3653,7 +3672,7 @@ void Message::fromNameUpdated(int width) const { const auto nameText = [&]() -> const Ui::Text::String * { if (from) { return &_fromName; - } else if (const auto info = item->hiddenSenderInfo()) { + } else if (const auto info= item->originalHiddenSenderInfo()) { return &info->nameText(); } else { Unexpected("Corrupted forwarded information in message."); @@ -3737,7 +3756,7 @@ QRect Message::countGeometry() const { const auto availableWidth = width() - st::msgMargin.left() - (centeredView ? st::msgMargin.left() : st::msgMargin.right()); - auto contentLeft = (outbg && !delegate()->elementIsChatWide()) + auto contentLeft = hasRightLayout() ? st::msgMargin.right() : st::msgMargin.left(); auto contentWidth = availableWidth; @@ -3791,7 +3810,7 @@ Ui::BubbleRounding Message::countMessageRounding() const { || (keyboard != nullptr) || item->isFakeBotAbout() || (context() == Context::Replies && item->isDiscussionPost()); - const auto right = !delegate()->elementIsChatWide() && hasOutLayout(); + const auto right = hasRightLayout(); using Corner = Ui::BubbleCornerRounding; return Ui::BubbleRounding{ .topLeft = (smallTop && !right) ? Corner::Small : Corner::Large, @@ -3991,7 +4010,7 @@ int Message::resizeContentGetHeight(int newWidth) { : contentWidth; newHeight += st::mediaInBubbleSkip + _reactions->resizeGetHeight(reactionsWidth); - if (hasOutLayout() && !delegate()->elementIsChatWide()) { + if (hasRightLayout()) { _reactions->flipToRight(); } } diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 4b75f21fa..e849d5f63 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -173,8 +173,6 @@ private: void initPsa(); void fromNameUpdated(int width) const; - [[nodiscard]] bool showForwardsFromSender( - not_null forwarded) const; [[nodiscard]] TextSelection skipTextSelection( TextSelection selection) const; [[nodiscard]] TextSelection unskipTextSelection( diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 68f95177b..a4bfdabb6 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -374,7 +374,6 @@ private: }; - class RepliesMemento final : public Window::SectionMemento { public: RepliesMemento( diff --git a/Telegram/SourceFiles/history/view/history_view_reply.cpp b/Telegram/SourceFiles/history/view/history_view_reply.cpp index 47be99c52..794ad323e 100644 --- a/Telegram/SourceFiles/history/view/history_view_reply.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reply.cpp @@ -212,7 +212,7 @@ void Reply::update( ? _externalSender : nullptr; _hiddenSenderColorIndexPlusOne = (!_colorPeer && message) - ? (message->hiddenSenderInfo()->colorIndex + 1) + ? (message->originalHiddenSenderInfo()->colorIndex + 1) : 0; const auto hasPreview = (story && story->hasReplyPreview()) @@ -380,8 +380,8 @@ QString Reply::senderName( const auto forwarded = data->resolvedMessage->Get(); if (forwarded) { - Assert(forwarded->hiddenSenderInfo != nullptr); - return forwarded->hiddenSenderInfo->name; + Assert(forwarded->originalHiddenSenderInfo != nullptr); + return forwarded->originalHiddenSenderInfo->name; } } return QString(); diff --git a/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp new file mode 100644 index 000000000..5664029bb --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp @@ -0,0 +1,657 @@ +/* +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 "history/view/history_view_sublist_section.h" + +#include "main/main_session.h" +#include "data/data_saved_messages.h" +#include "data/data_saved_sublist.h" +#include "data/data_session.h" +#include "data/data_peer_values.h" +#include "data/data_user.h" +#include "history/view/history_view_top_bar_widget.h" +#include "history/view/history_view_translate_bar.h" +#include "history/view/history_view_list_widget.h" +#include "history/history.h" +#include "history/history_item.h" +#include "lang/lang_keys.h" +#include "ui/chat/chat_style.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/scroll_area.h" +#include "ui/widgets/shadow.h" +#include "window/window_session_controller.h" +#include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_window.h" + +namespace HistoryView { +namespace { + +} // namespace + +SublistMemento::SublistMemento(not_null sublist) +: _sublist(sublist) { + const auto selfId = sublist->session().userPeerId(); + _list.setAroundPosition({ + .fullId = FullMsgId(selfId, ShowAtUnreadMsgId), + .date = TimeId(0), + }); +} + +object_ptr SublistMemento::createWidget( + QWidget *parent, + not_null controller, + Window::Column column, + const QRect &geometry) { + if (column == Window::Column::Third) { + return nullptr; + } + auto result = object_ptr( + parent, + controller, + _sublist); + result->setInternalState(geometry, this); + return result; +} + +SublistWidget::SublistWidget( + QWidget *parent, + not_null controller, + not_null sublist) +: Window::SectionWidget(parent, controller, sublist->peer()) +, _sublist(sublist) +, _history(sublist->owner().history(sublist->session().user())) +, _topBar(this, controller) +, _topBarShadow(this) +, _translateBar(std::make_unique(this, controller, _history)) +, _scroll(std::make_unique( + this, + controller->chatStyle()->value(lifetime(), st::historyScroll), + false)) +, _cornerButtons( + _scroll.get(), + controller->chatStyle(), + static_cast(this)) { + controller->chatStyle()->paletteChanged( + ) | rpl::start_with_next([=] { + _scroll->updateBars(); + }, _scroll->lifetime()); + + setupOpenChatButton(); + setupAboutHiddenAuthor(); + + Window::ChatThemeValueFromPeer( + controller, + sublist->peer() + ) | rpl::start_with_next([=](std::shared_ptr &&theme) { + _theme = std::move(theme); + controller->setChatStyleTheme(_theme); + }, lifetime()); + + _topBar->setActiveChat( + TopBarWidget::ActiveChat{ + .key = sublist, + .section = Dialogs::EntryState::Section::SavedSublist, + }, + nullptr); + + _topBar->move(0, 0); + _topBar->resizeToWidth(width()); + _topBar->show(); + + _topBar->deleteSelectionRequest( + ) | rpl::start_with_next([=] { + confirmDeleteSelected(); + }, _topBar->lifetime()); + _topBar->forwardSelectionRequest( + ) | rpl::start_with_next([=] { + confirmForwardSelected(); + }, _topBar->lifetime()); + _topBar->clearSelectionRequest( + ) | rpl::start_with_next([=] { + clearSelected(); + }, _topBar->lifetime()); + + _translateBar->raise(); + _topBarShadow->raise(); + controller->adaptive().value( + ) | rpl::start_with_next([=] { + updateAdaptiveLayout(); + }, lifetime()); + + _inner = _scroll->setOwnedWidget(object_ptr( + this, + controller, + static_cast(this))); + _scroll->move(0, _topBar->height()); + _scroll->show(); + _scroll->scrolls( + ) | rpl::start_with_next([=] { + onScroll(); + }, lifetime()); + + setupTranslateBar(); +} + +SublistWidget::~SublistWidget() = default; + +void SublistWidget::setupOpenChatButton() { + if (_sublist->peer()->isSavedHiddenAuthor()) { + return; + } + _openChatButton = std::make_unique( + this, + (_sublist->peer()->isBroadcast() + ? tr::lng_saved_open_channel(tr::now) + : _sublist->peer()->isUser() + ? tr::lng_saved_open_chat(tr::now) + : tr::lng_saved_open_group(tr::now)), + st::historyComposeButton); + + _openChatButton->setClickedCallback([=] { + controller()->showPeerHistory( + _sublist->peer(), + Window::SectionShow::Way::Forward); + }); +} + +void SublistWidget::setupAboutHiddenAuthor() { + if (!_sublist->peer()->isSavedHiddenAuthor()) { + return; + } + _aboutHiddenAuthor = std::make_unique(this); + _aboutHiddenAuthor->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(_aboutHiddenAuthor.get()); + auto rect = _aboutHiddenAuthor->rect(); + + p.fillRect(rect, st::historyReplyBg); + + p.setFont(st::normalFont); + p.setPen(st::windowSubTextFg); + p.drawText( + rect.marginsRemoved( + QMargins(st::historySendPadding, 0, st::historySendPadding, 0)), + tr::lng_saved_about_hidden(tr::now), + style::al_center); + }, _aboutHiddenAuthor->lifetime()); +} + +void SublistWidget::setupTranslateBar() { + controller()->adaptive().oneColumnValue( + ) | rpl::start_with_next([=, raw = _translateBar.get()](bool one) { + raw->setShadowGeometryPostprocess([=](QRect geometry) { + if (!one) { + geometry.setLeft(geometry.left() + st::lineWidth); + } + return geometry; + }); + }, _translateBar->lifetime()); + + _translateBarHeight = 0; + _translateBar->heightValue( + ) | rpl::start_with_next([=](int height) { + if (const auto delta = height - _translateBarHeight) { + _translateBarHeight = height; + setGeometryWithTopMoved(geometry(), delta); + } + }, _translateBar->lifetime()); + + _translateBar->finishAnimating(); +} + +void SublistWidget::cornerButtonsShowAtPosition( + Data::MessagePosition position) { + showAtPosition(position); +} + +Data::Thread *SublistWidget::cornerButtonsThread() { + return nullptr; +} + +FullMsgId SublistWidget::cornerButtonsCurrentId() { + return {}; +} + +bool SublistWidget::cornerButtonsIgnoreVisibility() { + return animatingShow(); +} + +std::optional SublistWidget::cornerButtonsDownShown() { + const auto top = _scroll->scrollTop() + st::historyToDownShownAfter; + if (top < _scroll->scrollTopMax() || _cornerButtons.replyReturn()) { + return true; + } else if (_inner->loadedAtBottomKnown()) { + return !_inner->loadedAtBottom(); + } + return std::nullopt; +} + +bool SublistWidget::cornerButtonsUnreadMayBeShown() { + return _inner->loadedAtBottomKnown(); +} + +bool SublistWidget::cornerButtonsHas(CornerButtonType type) { + return (type == CornerButtonType::Down); +} + +void SublistWidget::showAtPosition( + Data::MessagePosition position, + FullMsgId originId) { + _inner->showAtPosition( + position, + {}, + _cornerButtons.doneJumpFrom(position.fullId, originId)); +} + +void SublistWidget::updateAdaptiveLayout() { + _topBarShadow->moveToLeft( + controller()->adaptive().isOneColumn() ? 0 : st::lineWidth, + _topBar->height()); +} + +not_null SublistWidget::sublist() const { + return _sublist; +} + +Dialogs::RowDescriptor SublistWidget::activeChat() const { + return { + _history, + FullMsgId(_history->peer->id, ShowAtUnreadMsgId) + }; +} + +QPixmap SublistWidget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { + _topBar->updateControlsVisibility(); + if (params.withTopBarShadow) _topBarShadow->hide(); + auto result = Ui::GrabWidget(this); + if (params.withTopBarShadow) _topBarShadow->show(); + _translateBar->hide(); + return result; +} + +void SublistWidget::checkActivation() { + _inner->checkActivation(); +} + +void SublistWidget::doSetInnerFocus() { + _inner->setFocus(); +} + +bool SublistWidget::showInternal( + not_null memento, + const Window::SectionShow ¶ms) { + if (auto logMemento = dynamic_cast(memento.get())) { + if (logMemento->getSublist() == sublist()) { + restoreState(logMemento); + return true; + } + } + return false; +} + +void SublistWidget::setInternalState( + const QRect &geometry, + not_null memento) { + setGeometry(geometry); + Ui::SendPendingMoveResizeEvents(this); + restoreState(memento); +} + +std::shared_ptr SublistWidget::createMemento() { + auto result = std::make_shared(sublist()); + saveState(result.get()); + return result; +} + +bool SublistWidget::showMessage( + PeerId peerId, + const Window::SectionShow ¶ms, + MsgId messageId) { + return false; // We want 'Go to original' to work. +} + +void SublistWidget::saveState(not_null memento) { + _inner->saveState(memento->list()); +} + +void SublistWidget::restoreState(not_null memento) { + _inner->restoreState(memento->list()); +} + +void SublistWidget::resizeEvent(QResizeEvent *e) { + if (!width() || !height()) { + return; + } + recountChatWidth(); + updateControlsGeometry(); +} + +void SublistWidget::recountChatWidth() { + auto layout = (width() < st::adaptiveChatWideWidth) + ? Window::Adaptive::ChatLayout::Normal + : Window::Adaptive::ChatLayout::Wide; + controller()->adaptive().setChatLayout(layout); +} + +void SublistWidget::updateControlsGeometry() { + const auto contentWidth = width(); + + const auto newScrollTop = _scroll->isHidden() + ? std::nullopt + : base::make_optional(_scroll->scrollTop() + topDelta()); + _topBar->resizeToWidth(contentWidth); + _topBarShadow->resize(contentWidth, st::lineWidth); + + auto bottom = height(); + if (_openChatButton) { + _openChatButton->resizeToWidth(width()); + bottom -= _openChatButton->height(); + _openChatButton->move(0, bottom); + } + if (_aboutHiddenAuthor) { + _aboutHiddenAuthor->resize(width(), st::historyUnblock.height); + bottom -= _aboutHiddenAuthor->height(); + _aboutHiddenAuthor->move(0, bottom); + } + const auto controlsHeight = 0; + auto top = _topBar->height(); + _translateBar->move(0, top); + _translateBar->resizeToWidth(contentWidth); + top += _translateBarHeight; + const auto scrollHeight = bottom - top - controlsHeight; + const auto scrollSize = QSize(contentWidth, scrollHeight); + if (_scroll->size() != scrollSize) { + _skipScrollEvent = true; + _scroll->resize(scrollSize); + _inner->resizeToWidth(scrollSize.width(), _scroll->height()); + _skipScrollEvent = false; + } + _scroll->move(0, top); + if (!_scroll->isHidden()) { + if (newScrollTop) { + _scroll->scrollToY(*newScrollTop); + } + updateInnerVisibleArea(); + } + + _cornerButtons.updatePositions(); +} + +void SublistWidget::paintEvent(QPaintEvent *e) { + if (animatingShow()) { + SectionWidget::paintEvent(e); + return; + } else if (controller()->contentOverlapped(this, e)) { + return; + } + + const auto aboveHeight = _topBar->height(); + const auto bg = e->rect().intersected( + QRect(0, aboveHeight, width(), height() - aboveHeight)); + SectionWidget::PaintBackground(controller(), _theme.get(), this, bg); +} + +void SublistWidget::onScroll() { + if (_skipScrollEvent) { + return; + } + updateInnerVisibleArea(); +} + +void SublistWidget::updateInnerVisibleArea() { + const auto scrollTop = _scroll->scrollTop(); + _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); + _cornerButtons.updateJumpDownVisibility(); + _cornerButtons.updateUnreadThingsVisibility(); +} + +void SublistWidget::showAnimatedHook( + const Window::SectionSlideParams ¶ms) { + _topBar->setAnimatingMode(true); + if (params.withTopBarShadow) { + _topBarShadow->show(); + } +} + +void SublistWidget::showFinishedHook() { + _topBar->setAnimatingMode(false); + _inner->showFinished(); + _translateBar->show(); +} + +bool SublistWidget::floatPlayerHandleWheelEvent(QEvent *e) { + return _scroll->viewportEvent(e); +} + +QRect SublistWidget::floatPlayerAvailableRect() { + return mapToGlobal(_scroll->geometry()); +} + +Context SublistWidget::listContext() { + return Context::SavedSublist; +} + +bool SublistWidget::listScrollTo(int top, bool syntetic) { + top = std::clamp(top, 0, _scroll->scrollTopMax()); + if (_scroll->scrollTop() == top) { + updateInnerVisibleArea(); + return false; + } + _scroll->scrollToY(top); + return true; +} + +void SublistWidget::listCancelRequest() { + if (_inner && !_inner->getSelectedIds().empty()) { + clearSelected(); + return; + } + controller()->showBackFromStack(); +} + +void SublistWidget::listDeleteRequest() { + confirmDeleteSelected(); +} + +void SublistWidget::listTryProcessKeyInput(not_null e) { +} + +rpl::producer SublistWidget::listSource( + Data::MessagePosition aroundId, + int limitBefore, + int limitAfter) { + const auto messageId = aroundId.fullId.msg + ? aroundId.fullId.msg + : (ServerMaxMsgId - 1); + return [=](auto consumer) { + const auto pushSlice = [=] { + auto result = Data::MessagesSlice(); + result.fullCount = _sublist->fullCount(); + _topBar->setCustomTitle(result.fullCount + ? tr::lng_forum_messages( + tr::now, + lt_count_decimal, + *result.fullCount) + : tr::lng_contacts_loading(tr::now)); + const auto &messages = _sublist->messages(); + const auto i = ranges::lower_bound( + messages, + messageId, + ranges::greater(), + [](not_null item) { return item->id; }); + const auto before = int(end(messages) - i); + const auto useBefore = std::min(before, limitBefore); + const auto after = int(i - begin(messages)); + const auto useAfter = std::min(after, limitAfter); + const auto from = i - useAfter; + const auto till = i + useBefore; + auto nearestDistance = std::numeric_limits::max(); + result.ids.reserve(useAfter + useBefore); + for (auto j = till; j != from;) { + const auto item = *--j; + result.ids.push_back(item->fullId()); + const auto distance = std::abs((messageId - item->id).bare); + if (nearestDistance > distance) { + nearestDistance = distance; + result.nearestToAround = result.ids.back(); + } + } + result.skippedAfter = after - useAfter; + result.skippedBefore = result.fullCount + ? (*result.fullCount - after - useBefore) + : std::optional(); + if (!result.fullCount || useBefore < limitBefore) { + _sublist->owner().savedMessages().loadMore(_sublist); + } + consumer.put_next(std::move(result)); + }; + auto lifetime = rpl::lifetime(); + _sublist->changes() | rpl::start_with_next(pushSlice, lifetime); + pushSlice(); + return lifetime; + }; +} + +bool SublistWidget::listAllowsMultiSelect() { + return true; +} + +bool SublistWidget::listIsItemGoodForSelection( + not_null item) { + return item->isRegular() && !item->isService(); +} + +bool SublistWidget::listIsLessInOrder( + not_null first, + not_null second) { + return first->id < second->id; +} + +void SublistWidget::listSelectionChanged(SelectedItems &&items) { + HistoryView::TopBarWidget::SelectedState state; + state.count = items.size(); + for (const auto &item : items) { + if (item.canDelete) { + ++state.canDeleteCount; + } + if (item.canForward) { + ++state.canForwardCount; + } + } + _topBar->showSelected(state); +} + +void SublistWidget::listMarkReadTill(not_null item) { +} + +void SublistWidget::listMarkContentsRead( + const base::flat_set> &items) { +} + +MessagesBarData SublistWidget::listMessagesBar( + const std::vector> &elements) { + return {}; +} + +void SublistWidget::listContentRefreshed() { +} + +void SublistWidget::listUpdateDateLink( + ClickHandlerPtr &link, + not_null view) { +} + +bool SublistWidget::listElementHideReply(not_null view) { + return false; +} + +bool SublistWidget::listElementShownUnread(not_null view) { + return view->data()->unread(view->data()->history()); +} + +bool SublistWidget::listIsGoodForAroundPosition( + not_null view) { + return view->data()->isRegular(); +} + +void SublistWidget::listSendBotCommand( + const QString &command, + const FullMsgId &context) { +} + +void SublistWidget::listHandleViaClick(not_null bot) { +} + +not_null SublistWidget::listChatTheme() { + return _theme.get(); +} + +CopyRestrictionType SublistWidget::listCopyRestrictionType( + HistoryItem *item) { + return CopyRestrictionTypeFor(_history->peer, item); +} + +CopyRestrictionType SublistWidget::listCopyMediaRestrictionType( + not_null item) { + return CopyMediaRestrictionTypeFor(_history->peer, item); +} + +CopyRestrictionType SublistWidget::listSelectRestrictionType() { + return SelectRestrictionTypeFor(_history->peer); +} + +auto SublistWidget::listAllowedReactionsValue() +-> rpl::producer { + return Data::PeerAllowedReactionsValue(_history->peer); +} + +void SublistWidget::listShowPremiumToast(not_null document) { +} + +void SublistWidget::listOpenPhoto( + not_null photo, + FullMsgId context) { + controller()->openPhoto(photo, { context }); +} + +void SublistWidget::listOpenDocument( + not_null document, + FullMsgId context, + bool showInMediaView) { + controller()->openDocument(document, showInMediaView, { context }); +} + +void SublistWidget::listPaintEmpty( + Painter &p, + const Ui::ChatPaintContext &context) { +} + +QString SublistWidget::listElementAuthorRank(not_null view) { + return {}; +} + +History *SublistWidget::listTranslateHistory() { + return _history; +} + +void SublistWidget::listAddTranslatedItems( + not_null tracker) { +} + +void SublistWidget::confirmDeleteSelected() { + ConfirmDeleteSelectedItems(_inner); +} + +void SublistWidget::confirmForwardSelected() { + ConfirmForwardSelectedItems(_inner); +} + +void SublistWidget::clearSelected() { + _inner->cancelSelection(); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_sublist_section.h b/Telegram/SourceFiles/history/view/history_view_sublist_section.h new file mode 100644 index 000000000..008f521c5 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_sublist_section.h @@ -0,0 +1,217 @@ +/* +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 "window/section_widget.h" +#include "window/section_memento.h" +#include "history/view/history_view_list_widget.h" +#include "history/view/history_view_corner_buttons.h" +#include "data/data_messages.h" +#include "base/weak_ptr.h" +#include "base/timer.h" + +class History; + +namespace Ui { +class ScrollArea; +class PlainShadow; +class FlatButton; +} // namespace Ui + +namespace Profile { +class BackButton; +} // namespace Profile + +namespace HistoryView { + +class Element; +class TopBarWidget; +class SublistMemento; +class TranslateBar; + +class SublistWidget final + : public Window::SectionWidget + , private ListDelegate + , private CornerButtonsDelegate { +public: + SublistWidget( + QWidget *parent, + not_null controller, + not_null sublist); + ~SublistWidget(); + + [[nodiscard]] not_null sublist() const; + Dialogs::RowDescriptor activeChat() const override; + + bool hasTopBarShadow() const override { + return true; + } + + QPixmap grabForShowAnimation( + const Window::SectionSlideParams ¶ms) override; + + bool showInternal( + not_null memento, + const Window::SectionShow ¶ms) override; + std::shared_ptr createMemento() override; + bool showMessage( + PeerId peerId, + const Window::SectionShow ¶ms, + MsgId messageId) override; + + void setInternalState( + const QRect &geometry, + not_null memento); + + Window::SectionActionResult sendBotCommand( + Bot::SendCommandRequest request) override { + return Window::SectionActionResult::Fallback; + } + + // Float player interface. + bool floatPlayerHandleWheelEvent(QEvent *e) override; + QRect floatPlayerAvailableRect() override; + + // ListDelegate interface. + Context listContext() override; + bool listScrollTo(int top, bool syntetic = true) override; + void listCancelRequest() override; + void listDeleteRequest() override; + void listTryProcessKeyInput(not_null e) override; + rpl::producer listSource( + Data::MessagePosition aroundId, + int limitBefore, + int limitAfter) override; + bool listAllowsMultiSelect() override; + bool listIsItemGoodForSelection(not_null item) override; + bool listIsLessInOrder( + not_null first, + not_null second) override; + void listSelectionChanged(SelectedItems &&items) override; + void listMarkReadTill(not_null item) override; + void listMarkContentsRead( + const base::flat_set> &items) override; + MessagesBarData listMessagesBar( + const std::vector> &elements) override; + void listContentRefreshed() override; + void listUpdateDateLink( + ClickHandlerPtr &link, + not_null view) override; + bool listElementHideReply(not_null view) override; + bool listElementShownUnread(not_null view) override; + bool listIsGoodForAroundPosition(not_null view) override; + void listSendBotCommand( + const QString &command, + const FullMsgId &context) override; + void listHandleViaClick(not_null bot) override; + not_null listChatTheme() override; + CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override; + CopyRestrictionType listCopyMediaRestrictionType( + not_null item) override; + CopyRestrictionType listSelectRestrictionType() override; + auto listAllowedReactionsValue() + -> rpl::producer override; + void listShowPremiumToast(not_null document) override; + void listOpenPhoto( + not_null photo, + FullMsgId context) override; + void listOpenDocument( + not_null document, + FullMsgId context, + bool showInMediaView) override; + void listPaintEmpty( + Painter &p, + const Ui::ChatPaintContext &context) override; + QString listElementAuthorRank(not_null view) override; + History *listTranslateHistory() override; + void listAddTranslatedItems( + not_null tracker) override; + + // CornerButtonsDelegate delegate. + void cornerButtonsShowAtPosition( + Data::MessagePosition position) override; + Data::Thread *cornerButtonsThread() override; + FullMsgId cornerButtonsCurrentId() override; + bool cornerButtonsIgnoreVisibility() override; + std::optional cornerButtonsDownShown() override; + bool cornerButtonsUnreadMayBeShown() override; + bool cornerButtonsHas(CornerButtonType type) override; + +private: + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + + void showAnimatedHook( + const Window::SectionSlideParams ¶ms) override; + void showFinishedHook() override; + void doSetInnerFocus() override; + void checkActivation() override; + + void onScroll(); + void updateInnerVisibleArea(); + void updateControlsGeometry(); + void updateAdaptiveLayout(); + void saveState(not_null memento); + void restoreState(not_null memento); + void showAtPosition( + Data::MessagePosition position, + FullMsgId originId = {}); + + void setupOpenChatButton(); + void setupAboutHiddenAuthor(); + void setupTranslateBar(); + + void confirmDeleteSelected(); + void confirmForwardSelected(); + void clearSelected(); + void recountChatWidth(); + + const not_null _sublist; + const not_null _history; + std::shared_ptr _theme; + QPointer _inner; + object_ptr _topBar; + object_ptr _topBarShadow; + + std::unique_ptr _translateBar; + int _translateBarHeight = 0; + + bool _skipScrollEvent = false; + std::unique_ptr _scroll; + std::unique_ptr _openChatButton; + std::unique_ptr _aboutHiddenAuthor; + + CornerButtons _cornerButtons; + +}; + +class SublistMemento : public Window::SectionMemento { +public: + explicit SublistMemento(not_null sublist); + + object_ptr createWidget( + QWidget *parent, + not_null controller, + Window::Column column, + const QRect &geometry) override; + + [[nodiscard]] not_null getSublist() const { + return _sublist; + } + + [[nodiscard]] not_null list() { + return &_list; + } + +private: + const not_null _sublist; + ListMemento _list; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 0fd011ec7..445a7657d 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer_values.h" #include "data/data_group_call.h" // GroupCall::input. #include "data/data_folder.h" +#include "data/data_saved_sublist.h" #include "data/data_session.h" #include "data/data_stories.h" #include "data/data_channel.h" @@ -472,9 +473,17 @@ void TopBarWidget::paintTopBar(Painter &p) { } const auto now = crl::now(); - const auto history = _activeChat.key.owningHistory(); + const auto peer = _activeChat.key.owningHistory() + ? _activeChat.key.owningHistory()->peer.get() + : nullptr; const auto folder = _activeChat.key.folder(); + const auto sublist = _activeChat.key.sublist(); const auto topic = _activeChat.key.topic(); + const auto history = _activeChat.key.history(); + const auto namePeer = history + ? history->peer.get() + : sublist ? sublist->peer().get() + : nullptr; if (topic && _activeChat.section == Section::Replies) { p.setPen(st::dialogsNameFg); topic->chatListNameText().drawElided( @@ -497,22 +506,22 @@ void TopBarWidget::paintTopBar(Painter &p) { p.drawTextLeft(nameleft, statustop, width(), _customTitleText); } } else if (folder - || history->peer->sharedMediaInfo() + || (peer && peer->sharedMediaInfo()) || (_activeChat.section == Section::Scheduled) || (_activeChat.section == Section::Pinned)) { auto text = (_activeChat.section == Section::Scheduled) - ? ((history && history->peer->isSelf()) + ? ((peer && peer->isSelf()) ? tr::lng_reminder_messages(tr::now) : tr::lng_scheduled_messages(tr::now)) : (_activeChat.section == Section::Pinned) ? _customTitleText : folder ? folder->chatListName() - : history->peer->isSelf() + : peer->isSelf() ? tr::lng_saved_messages(tr::now) - : history->peer->isRepliesChat() + : peer->isRepliesChat() ? tr::lng_replies_messages(tr::now) - : history->peer->name(); + : peer->name(); const auto textWidth = st::historySavedFont->width(text); if (availableWidth < textWidth) { text = st::historySavedFont->elided(text, availableWidth); @@ -543,16 +552,14 @@ void TopBarWidget::paintTopBar(Painter &p) { width(), st::historyStatusFgTyping, now)) { - p.setPen(st::historyStatusFg); - p.drawTextLeft(nameleft, statustop, width(), _customTitleText); + paintStatus(p, nameleft, statustop, availableWidth, width()); } - } else if (const auto history = _activeChat.key.history()) { - const auto peer = history->peer; - if (_titleNameVersion < peer->nameVersion()) { - _titleNameVersion = peer->nameVersion(); + } else if (namePeer) { + if (_titleNameVersion < namePeer->nameVersion()) { + _titleNameVersion = namePeer->nameVersion(); _title.setText( st::msgNameStyle, - peer->topBarNameText(), + namePeer->topBarNameText(), Ui::NameTextOptions()); } const auto badgeWidth = _titleBadge.drawGetWidth( @@ -565,7 +572,7 @@ void TopBarWidget::paintTopBar(Painter &p) { _title.maxWidth(), width(), { - .peer = peer, + .peer = namePeer, .verified = &st::dialogsVerifiedIcon, .premium = &st::dialogsPremiumIcon.icon, .scam = &st::attentionButtonFg, @@ -607,6 +614,9 @@ bool TopBarWidget::paintSendAction( int outerWidth, style::color fg, crl::time now) { + if (!_sendAction) { + return false; + } const auto seen = _emojiInteractionSeen.get(); if (!seen || seen->till <= now) { return _sendAction->paint(p, x, y, availableWidth, outerWidth, fg, now); @@ -657,10 +667,22 @@ void TopBarWidget::paintStatus( int top, int availableWidth, int outerWidth) { - p.setPen(_titlePeerTextOnline - ? st::historyStatusFgActive - : st::historyStatusFg); - _titlePeerText.drawLeftElided(p, left, top, availableWidth, outerWidth); + using Section = Dialogs::EntryState::Section; + const auto section = _activeChat.section; + if (section == Section::Replies || section == Section::SavedSublist) { + p.setPen(st::historyStatusFg); + p.drawTextLeft(left, top, outerWidth, _customTitleText); + } else { + p.setPen(_titlePeerTextOnline + ? st::historyStatusFgActive + : st::historyStatusFg); + _titlePeerText.drawLeftElided( + p, + left, + top, + availableWidth, + outerWidth); + } } QRect TopBarWidget::getMembersShowAreaGeometry() const { @@ -694,11 +716,15 @@ void TopBarWidget::infoClicked() { return; } else if (const auto topic = key.topic()) { _controller->showSection(std::make_shared(topic)); - } else if (key.peer()->isSelf()) { + } else if (const auto sublist = key.sublist()) { + _controller->showSection(std::make_shared( + _controller->session().user(), + Info::Section(Storage::SharedMediaType::Photo))); + } else if (key.peer()->savedSublistsInfo()) { _controller->showSection(std::make_shared( key.peer(), - Info::Section(Storage::SharedMediaType::Photo))); - } else if (key.peer()->isRepliesChat()) { + Info::Section::Type::SavedSublists)); + } else if (key.peer()->sharedMediaInfo()) { _controller->showSection(std::make_shared( key.peer(), Info::Section(Storage::SharedMediaType::Photo))); diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 04df9f2aa..226285f72 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -9,12 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/random.h" #include "lang/lang_keys.h" +#include "lottie/lottie_icon.h" #include "storage/localstorage.h" #include "main/main_session.h" #include "media/player/media_player_float.h" // Media::Player::RoundPainter. #include "media/audio/media_audio.h" #include "media/player/media_player_instance.h" #include "history/history_item_components.h" +#include "history/history_item_helpers.h" // ClearMediaAsExpired. #include "history/history.h" #include "core/click_handler_types.h" // kDocumentFilenameTooltipProperty. #include "history/view/history_view_element.h" @@ -30,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/cached_round_corners.h" #include "ui/painter.h" #include "ui/power_saving.h" +#include "ui/rect.h" #include "ui/ui_utility.h" #include "data/data_session.h" #include "data/data_document.h" @@ -41,12 +44,101 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_transcribes.h" #include "apiwrap.h" #include "styles/style_chat.h" +#include "styles/style_dialogs.h" namespace HistoryView { namespace { constexpr auto kAudioVoiceMsgUpdateView = crl::time(100); +void DrawCornerBadgeTTL( + QPainter &p, + const style::color &color, + const QRect &circleRect) { + p.save(); + const auto partRect = QRect( + circleRect.left() + circleRect.width() - st::dialogsTTLBadgeSize * 0.85, + circleRect.top() + circleRect.height() - st::dialogsTTLBadgeSize * 0.85, + st::dialogsTTLBadgeSize, + st::dialogsTTLBadgeSize); + + auto hq = PainterHighQualityEnabler(p); + p.setBrush(color); + p.drawEllipse(partRect); + + const auto innerRect = partRect - st::dialogsTTLBadgeInnerMargins; + const auto ttlText = u"1"_q; + + p.setFont(st::dialogsScamFont); + p.setPen(st::premiumButtonFg); + p.drawText(innerRect, ttlText, style::al_center); + + constexpr auto kPenWidth = 1.5; + + const auto penWidth = style::ConvertScaleExact(kPenWidth); + auto pen = QPen(st::premiumButtonFg); + pen.setJoinStyle(Qt::RoundJoin); + pen.setCapStyle(Qt::RoundCap); + pen.setWidthF(penWidth); + + p.setPen(pen); + p.setBrush(Qt::NoBrush); + p.drawArc(innerRect, arc::kQuarterLength, arc::kHalfLength); + + p.setClipRect(innerRect + - QMargins(innerRect.width() / 2, 0, -penWidth, -penWidth)); + pen.setStyle(Qt::DotLine); + p.setPen(pen); + p.drawEllipse(innerRect); + p.restore(); +} + +[[nodiscard]] HistoryView::TtlPaintCallback CreateTtlPaintCallback( + std::shared_ptr lifetime, + Fn update) { + struct State final { + std::unique_ptr start; + std::unique_ptr idle; + }; + const auto iconSize = Size(std::min( + st::historyFileInPause.width(), + st::historyFileInPause.height())); + const auto state = lifetime->make_state(); + state->start = Lottie::MakeIcon({ + .name = u"voice_ttl_start"_q, + .color = &st::historyFileInIconFg, + .sizeOverride = iconSize, + }); + + const auto animateSingle = [=]( + not_null icon, + Fn next) { + auto callback = [=] { + update(); + if (icon->frameIndex() == icon->framesCount()) { + next(); + } + }; + icon->animate(std::move(callback), 0, icon->framesCount()); + }; + const auto animate = [=](auto reanimate) -> void { + animateSingle(state->idle.get(), [=] { reanimate(reanimate); }); + }; + animateSingle( + state->start.get(), + [=] { + state->idle = Lottie::MakeIcon({ + .name = u"voice_ttl_idle"_q, + .color = &st::historyFileInIconFg, + .sizeOverride = iconSize, + }); + animate(animate); + }); + return [=](QPainter &p, QRect r, QColor c) { + (state->idle ? state->idle : state->start)->paintInCenter(p, r, c); + }; +} + [[nodiscard]] QString CleanTagSymbols(const QString &value) { auto result = QString(); const auto begin = value.begin(), end = value.end(); @@ -195,7 +287,8 @@ Document::Document( not_null document) : File(parent, realParent) , _data(document) { - if (_data->isVideoMessage()) { + const auto isRound = _data->isVideoMessage(); + if (isRound) { const auto &entry = _data->session().api().transcribes().entry( realParent); _transcribedRound = entry.shown; @@ -209,7 +302,44 @@ Document::Document( _tooltipFilename.setTooltipText(named->name); } - setDocumentLinks(_data, realParent); + if ((_data->isVoiceMessage() || isRound) + && IsVoiceOncePlayable(_parent->data())) { + _parent->data()->removeFromSharedMediaIndex(); + setDocumentLinks(_data, realParent, [=] { + _openl = nullptr; + + auto lifetime = std::make_shared(); + rpl::merge( + ::Media::Player::instance()->updatedNotifier( + ) | rpl::filter([=](::Media::Player::TrackState state) { + using State = ::Media::Player::State; + const auto badState = state.state == State::Stopped + || state.state == State::StoppedAtEnd + || state.state == State::StoppedAtError + || state.state == State::StoppedAtStart; + return (state.id.contextId() != _realParent->fullId()) + && !badState; + }) | rpl::to_empty, + ::Media::Player::instance()->tracksFinished( + ) | rpl::filter([=](AudioMsgId::Type type) { + return (type == AudioMsgId::Type::Voice); + }) | rpl::to_empty + ) | rpl::start_with_next([=]() mutable { + _drawTtl = nullptr; + const auto item = _parent->data(); + if (lifetime) { + base::take(lifetime)->destroy(); + } + // Destroys this. + ClearMediaAsExpired(item); + }, *lifetime); + _drawTtl = CreateTtlPaintCallback(lifetime, [=] { repaint(); }); + + return false; + }); + } else { + setDocumentLinks(_data, realParent); + } setStatusSize(Ui::FileStatusSizeReady); @@ -273,9 +403,9 @@ void Document::createComponents(bool caption) { _realParent->fullId()); } if (const auto voice = Get()) { - voice->seekl = std::make_shared( - _data, - [](FullMsgId) {}); + voice->seekl = !IsVoiceOncePlayable(_parent->data()) + ? std::make_shared(_data, [](FullMsgId) {}) + : nullptr; if (_transcribedRound) { voice->round = std::make_unique<::Media::Player::RoundPainter>( _realParent); @@ -306,7 +436,9 @@ QSize Document::countOptimalSize() { const auto voice = Get(); if (voice) { const auto session = &_realParent->history()->session(); - if (!session->premium() && !session->api().transcribes().trialsSupport()) { + if (_parent->data()->media()->ttlSeconds() + || (!session->premium() + && !session->api().transcribes().trialsSupport())) { voice->transcribe = nullptr; voice->transcribeText = {}; } else { @@ -586,6 +718,10 @@ void Document::draw( PainterHighQualityEnabler hq(p); p.setBrush(stm->msgFileBg); p.drawEllipse(inner); + + if (_parent->data()->media()->ttlSeconds()) { + DrawCornerBadgeTTL(p, stm->msgFileBg, inner); + } } } @@ -622,7 +758,9 @@ void Document::draw( : nullptr; const auto paintContent = [&](QPainter &q) { - if (previous && radialOpacity > 0. && radialOpacity < 1.) { + if (_drawTtl) { + _drawTtl(q, inner, context.st->historyFileInIconFg()->c); + } else if (previous && radialOpacity > 0. && radialOpacity < 1.) { PaintInterpolatedIcon(q, icon, *previous, radialOpacity, inner); } else { icon.paintInCenter(q, inner); diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.h b/Telegram/SourceFiles/history/view/media/history_view_document.h index 126a7c777..5e57308ed 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.h +++ b/Telegram/SourceFiles/history/view/media/history_view_document.h @@ -25,6 +25,8 @@ class String; namespace HistoryView { +using TtlPaintCallback = Fn; + class Document final : public File , public RuntimeComposer { @@ -178,6 +180,8 @@ private: mutable TooltipFilename _tooltipFilename; + TtlPaintCallback _drawTtl; + bool _transcribedRound = false; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp index 140fdd86a..b229b5eb9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp @@ -259,7 +259,9 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const { InfoDisplayType::Image); } if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareLeft = _parent->hasRightLayout() + ? (paintx - size->width() - st::historyFastShareLeft) + : (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height()); _parent->drawRightAction(p, context, fastShareLeft, fastShareTop, 2 * paintx + paintw); } @@ -376,7 +378,9 @@ TextState ExtendedPreview::textState(QPoint point, StateRequest request) const { return bottomInfoResult; } if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareLeft = _parent->hasRightLayout() + ? (paintx - size->width() - st::historyFastShareLeft) + : (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height()); if (QRect(fastShareLeft, fastShareTop, size->width(), size->height()).contains(point)) { result.link = _parent->rightActionLink(point diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 42247802a..21d35d0a6 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/confirm_box.h" #include "ui/painter.h" #include "history/history_item_components.h" +#include "history/history_item_helpers.h" #include "history/history_item.h" #include "history/history.h" #include "history/view/history_view_element.h" @@ -363,7 +364,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const { auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); const bool bubble = _parent->hasBubble(); - const auto outbg = context.outbg; + const auto rightLayout = _parent->hasRightLayout(); const auto inWebPage = (_parent->media() != this); const auto isRound = _data->isVideoMessage(); const auto botTop = _parent->Get(); @@ -388,9 +389,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const { const auto via = unwrapped ? item->Get() : nullptr; const auto reply = unwrapped ? _parent->Get() : nullptr; const auto forwarded = unwrapped ? item->Get() : nullptr; - const auto rightAligned = unwrapped - && outbg - && !_parent->delegate()->elementIsChatWide(); + const auto rightAligned = unwrapped && rightLayout; if (via || reply || forwarded) { usew = maxWidth() - additionalWidth(reply, via, forwarded); if (rightAligned) { @@ -769,7 +768,9 @@ void Gif::draw(Painter &p, const PaintContext &context) const { const auto rightActionWidth = size ? size->width() : _transcribe->size().width(); - auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareLeft = rightLayout + ? (paintx + usex - size->width() - st::historyFastShareLeft) + : (fullRight + st::historyFastShareLeft); auto fastShareTop = fullBottom - st::historyFastShareBottom - (size ? size->height() : 0); @@ -788,8 +789,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const { if (_transcribe) { paintTranscribe(p, fastShareLeft, fastShareTop, true, context); } - } - if (rightAligned && _transcribe) { + } else if (rightAligned && _transcribe) { paintTranscribe(p, usex, fullBottom, false, context); } } @@ -1009,7 +1009,7 @@ TextState Gif::textState(QPoint point, StateRequest request) const { } painth -= st::mediaCaptionSkip; } - const auto outbg = _parent->hasOutLayout(); + const auto rightLayout = _parent->hasRightLayout(); const auto inWebPage = (_parent->media() != this); const auto isRound = _data->isVideoMessage(); const auto unwrapped = isUnwrapped(); @@ -1018,9 +1018,7 @@ TextState Gif::textState(QPoint point, StateRequest request) const { const auto via = unwrapped ? item->Get() : nullptr; const auto reply = unwrapped ? _parent->Get() : nullptr; const auto forwarded = unwrapped ? item->Get() : nullptr; - const auto rightAligned = unwrapped - && outbg - && !_parent->delegate()->elementIsChatWide(); + const auto rightAligned = unwrapped && rightLayout; if (via || reply || forwarded) { usew = maxWidth() - additionalWidth(reply, via, forwarded); if (rightAligned) { @@ -1157,7 +1155,9 @@ TextState Gif::textState(QPoint point, StateRequest request) const { } if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) { const auto rightActionWidth = size->width(); - auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareLeft = _parent->hasRightLayout() + ? (paintx + usex - size->width() - st::historyFastShareLeft) + : (fullRight + st::historyFastShareLeft); auto fastShareTop = fullBottom - st::historyFastShareBottom - size->height(); @@ -1531,9 +1531,7 @@ QRect Gif::contentRectForReactions() const { } auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto usex = 0, usew = paintw; - const auto outbg = _parent->hasOutLayout(); - const auto rightAligned = outbg - && !_parent->delegate()->elementIsChatWide(); + const auto rightAligned = _parent->hasRightLayout(); const auto item = _parent->data(); const auto via = item->Get(); const auto reply = _parent->Get(); @@ -1572,9 +1570,7 @@ QPoint Gif::resolveCustomInfoRightBottom() const { maxRight -= st::msgMargin.left(); } const auto infoWidth = _parent->infoWidth(); - const auto outbg = _parent->hasOutLayout(); - const auto rightAligned = outbg - && !_parent->delegate()->elementIsChatWide(); + const auto rightAligned = _parent->hasRightLayout(); if (!rightAligned) { // This is just some arbitrary point, // the main idea is to make info left aligned here. @@ -1974,6 +1970,7 @@ bool Gif::needCornerStatusDisplay() const { void Gif::ensureTranscribeButton() const { if (_data->isVideoMessage() + && !IsVoiceOncePlayable(_parent->data()) && (_data->session().premium() || _data->session().api().transcribes().trialsSupport())) { if (!_transcribe) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_location.cpp b/Telegram/SourceFiles/history/view/media/history_view_location.cpp index 293849ba3..5fab03d15 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_location.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_location.cpp @@ -235,7 +235,9 @@ void Location::draw(Painter &p, const PaintContext &context) const { paintx * 2 + paintw, InfoDisplayType::Image); if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareLeft = _parent->hasRightLayout() + ? (paintx - size->width() - st::historyFastShareLeft) + : (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height()); _parent->drawRightAction(p, context, fastShareLeft, fastShareTop, 2 * paintx + paintw); } @@ -331,7 +333,9 @@ TextState Location::textState(QPoint point, StateRequest request) const { return bottomInfoResult; } if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareLeft = _parent->hasRightLayout() + ? (paintx - size->width() - st::historyFastShareLeft) + : (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height()); if (QRect(fastShareLeft, fastShareTop, size->width(), size->height()).contains(point)) { result.link = _parent->rightActionLink(point diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 1ac40603c..379afd782 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -427,7 +427,9 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { InfoDisplayType::Image); } if (const auto size = _parent->hasBubble() ? std::nullopt : _parent->rightActionSize()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareLeft = _parent->hasRightLayout() + ? (-size->width() - st::historyFastShareLeft) + : (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height()); _parent->drawRightAction(p, context, fastShareLeft, fastShareTop, width()); } @@ -501,7 +503,9 @@ TextState GroupedMedia::textState(QPoint point, StateRequest request) const { return bottomInfoResult; } if (const auto size = _parent->hasBubble() ? std::nullopt : _parent->rightActionSize()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareLeft = _parent->hasRightLayout() + ? (-size->width() - st::historyFastShareLeft) + : (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height()); if (QRect(fastShareLeft, fastShareTop, size->width(), size->height()).contains(point)) { result.link = _parent->rightActionLink(point diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index b0df17d48..b0dba7ecb 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -81,8 +81,7 @@ QSize UnwrappedMedia::countCurrentSize(int newWidth) { if (_parent->media() != this) { return { newWidth, newHeight }; } - if (_parent->hasOutLayout() - && !_parent->delegate()->elementIsChatWide()) { + if (_parent->hasRightLayout()) { // Add some height to isolated emoji for the timestamp info. const auto infoHeight = st::msgDateImgPadding.y() * 2 + st::msgDateFont->height; @@ -137,8 +136,7 @@ void UnwrappedMedia::draw(Painter &p, const PaintContext &context) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { return; } - const auto rightAligned = context.outbg - && !_parent->delegate()->elementIsChatWide(); + const auto rightAligned = _parent->hasRightLayout(); const auto inWebPage = (_parent->media() != this); const auto item = _parent->data(); auto usex = 0; @@ -248,8 +246,7 @@ void UnwrappedMedia::drawSurrounding( const HistoryMessageForwarded *forwarded) const { const auto st = context.st; const auto sti = context.imageStyle(); - const auto rightAligned = context.outbg - && !_parent->delegate()->elementIsChatWide(); + const auto rightAligned = _parent->hasRightLayout(); const auto rightActionSize = _parent->rightActionSize(); const auto fullRight = calculateFullRight(inner); auto fullBottom = height(); @@ -262,6 +259,7 @@ void UnwrappedMedia::drawSurrounding( inner.x() * 2 + inner.width(), InfoDisplayType::Background); } + auto replyLeft = 0; auto replyRight = 0; auto rectw = _additionalOnTop ? std::min(width() - st::msgReplyPadding.left(), additionalWidth(topic, reply, via, forwarded)) @@ -340,11 +338,15 @@ void UnwrappedMedia::drawSurrounding( } reply->paint(p, _parent, context, rectx, recty, rectw, false); } + replyLeft = rectx; replyRight = rectx + rectw; } } if (rightActionSize) { const auto position = calculateFastActionPosition( + inner, + rightAligned, + replyLeft, replyRight, reply ? reply->height() : 0, fullBottom, @@ -360,8 +362,7 @@ PointState UnwrappedMedia::pointState(QPoint point) const { return PointState::Outside; } - const auto rightAligned = _parent->hasOutLayout() - && !_parent->delegate()->elementIsChatWide(); + const auto rightAligned = _parent->hasRightLayout(); const auto inWebPage = (_parent->media() != this); auto usex = 0; auto usew = _contentSize.width(); @@ -394,8 +395,7 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { return result; } - const auto rightAligned = _parent->hasOutLayout() - && !_parent->delegate()->elementIsChatWide(); + const auto rightAligned = _parent->hasRightLayout(); const auto inWebPage = (_parent->media() != this); const auto item = _parent->data(); auto usex = 0; @@ -420,6 +420,7 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { const auto reply = inWebPage ? nullptr : _parent->Get(); const auto topic = inWebPage ? nullptr : _parent->displayedTopicButton(); const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo(); + auto replyLeft = 0; auto replyRight = 0; auto rectw = _additionalOnTop ? std::min(width() - st::msgReplyPadding.left(), additionalWidth(topic, reply, via, forwarded)) @@ -491,7 +492,8 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { reply->createRippleAnimation(_parent, replyRect.size()); } } - replyRight = rectx + rectw - st::msgReplyPadding.right(); + replyLeft = rectx; + replyRight = rectx + rectw; } } const auto fullRight = calculateFullRight(inner); @@ -509,6 +511,9 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { } if (rightActionSize) { const auto position = calculateFastActionPosition( + inner, + rightAligned, + replyLeft, replyRight, reply ? reply->height() : 0, fullBottom, @@ -544,8 +549,7 @@ QRect UnwrappedMedia::contentRectForReactions() const { if (inWebPage) { return QRect(0, 0, width(), height()); } - const auto rightAligned = _parent->hasOutLayout() - && !_parent->delegate()->elementIsChatWide(); + const auto rightAligned = _parent->hasRightLayout(); auto usex = 0; auto usew = _contentSize.width(); accumulate_max(usew, _parent->reactionsOptimalWidth()); @@ -589,8 +593,7 @@ std::unique_ptr UnwrappedMedia::stickerTakePlayer( } int UnwrappedMedia::calculateFullRight(const QRect &inner) const { - const auto rightAligned = _parent->hasOutLayout() - && !_parent->delegate()->elementIsChatWide(); + const auto rightAligned = _parent->hasRightLayout(); const auto infoWidth = _parent->infoWidth() + st::msgDateImgPadding.x() * 2 + st::msgReplyPadding.left(); @@ -606,13 +609,19 @@ int UnwrappedMedia::calculateFullRight(const QRect &inner) const { auto fullRight = inner.x() + inner.width() + (rightAligned ? 0 : infoWidth); - if (fullRight + rightActionWidth + rightSkip > _parent->width()) { - fullRight = _parent->width() - rightActionWidth - rightSkip; + const auto rightActionSkip = rightAligned ? 0 : rightActionWidth; + if (fullRight + rightActionSkip + rightSkip > _parent->width()) { + fullRight = _parent->width() + - (rightAligned ? 0 : rightActionSkip) + - rightSkip; } return fullRight; } QPoint UnwrappedMedia::calculateFastActionPosition( + QRect inner, + bool rightAligned, + int replyLeft, int replyRight, int replyHeight, int fullBottom, @@ -623,9 +632,12 @@ QPoint UnwrappedMedia::calculateFastActionPosition( - size.height()); const auto doesRightActionHitReply = replyRight && (fastShareTop < replyHeight); - const auto fastShareLeft = ((doesRightActionHitReply - ? replyRight - : fullRight) + st::historyFastShareLeft); + const auto fastShareLeft = rightAligned + ? ((doesRightActionHitReply ? replyLeft : inner.x()) + - size.width() + - st::historyFastShareLeft) + : ((doesRightActionHitReply ? replyRight : fullRight) + + st::historyFastShareLeft); return QPoint(fastShareLeft, fastShareTop); } @@ -635,8 +647,7 @@ bool UnwrappedMedia::needInfoDisplay() const { || _parent->isUnderCursor() || _parent->rightActionSize() || _parent->isLastAndSelfMessage() - || (_parent->hasOutLayout() - && !_parent->delegate()->elementIsChatWide() + || (_parent->hasRightLayout() && _content->alwaysShowOutTimestamp()); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h index 1cd71c86a..0259392d6 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h @@ -146,6 +146,9 @@ private: int calculateFullRight(const QRect &inner) const; QPoint calculateFastActionPosition( + QRect inner, + bool rightAligned, + int replyLeft, int replyRight, int replyHeight, int fullBottom, diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index b5729c6fd..b87c0c6a3 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -455,7 +455,9 @@ void Photo::draw(Painter &p, const PaintContext &context) const { InfoDisplayType::Image); } if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareLeft = _parent->hasRightLayout() + ? (paintx - size->width() - st::historyFastShareLeft) + : (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height()); _parent->drawRightAction(p, context, fastShareLeft, fastShareTop, 2 * paintx + paintw); } @@ -731,7 +733,9 @@ TextState Photo::textState(QPoint point, StateRequest request) const { return bottomInfoResult; } if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareLeft = _parent->hasRightLayout() + ? (paintx - size->width() - st::historyFastShareLeft) + : (fullRight + st::historyFastShareLeft); auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height()); if (QRect(fastShareLeft, fastShareTop, size->width(), size->height()).contains(point)) { result.link = _parent->rightActionLink(point diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp index 403f97c19..20d242a64 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp @@ -417,8 +417,7 @@ bool Sticker::mirrorHorizontal() const { if (!hasPremiumEffect()) { return false; } - const auto rightAligned = _parent->hasOutLayout() - && !_parent->delegate()->elementIsChatWide(); + const auto rightAligned = _parent->hasRightLayout(); return !rightAligned; } 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 1c622ce2f..9ab8a6706 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -69,7 +69,8 @@ constexpr auto kMaxOriginalEntryLines = 8192; parent, *document, skipPremiumEffect, - spoiler)); + spoiler, + /*ttlSeconds = */0)); } else if (const auto photo = std::get_if(&item)) { result.push_back(std::make_unique( parent, diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp index 345512ea1..91ad93484 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp @@ -28,7 +28,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView::Reactions { namespace { -constexpr auto kDivider = 4; constexpr auto kToggleDuration = crl::time(120); constexpr auto kActivateDuration = crl::time(150); constexpr auto kExpandDuration = crl::time(300); diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index 496ab0484..e44d5c98e 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -812,7 +812,7 @@ void Selector::expand() { createList(); cacheExpandIcon(); - [[maybe_unused]] const auto grabbed = Ui::GrabWidget(_scroll); + [[maybe_unused]] const auto grabbed = Ui::GrabWidget(_scroll); // clazy:exclude=unused-non-trivial-variable _list->prepareExpanding(); setSelected(-1); diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index 3b06d0adb..01d6d5c5e 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -1056,7 +1056,7 @@ void CreateGiveawayBox( .adaptive = true, .filter = filter, }); - } else { + } else if (weak) { state->confirmButtonBusy = false; } }; diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 1ed7c486e..c709a7014 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -33,6 +33,10 @@ InfoTopBar { back: IconButton; title: FlatLabel; titlePosition: point; + titleWithSubtitle: FlatLabel; + titleWithSubtitlePosition: point; + subtitle: FlatLabel; + subtitlePosition: point; bg: color; mediaCancel: IconButton; mediaActionsSkip: pixels; @@ -186,6 +190,14 @@ infoTopBar: InfoTopBar { back: infoTopBarBack; title: infoTopBarTitle; titlePosition: point(24px, 17px); + titleWithSubtitle: FlatLabel(infoTopBarTitle) { + style: semiboldTextStyle; + } + titleWithSubtitlePosition: point(16px, 8px); + subtitle: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; + } + subtitlePosition: point(16px, 28px); bg: windowBg; mediaCancel: infoTopBarMediaCancel; mediaActionsSkip: 4px; @@ -261,6 +273,8 @@ infoLayerTopBar: InfoTopBar(infoTopBar) { back: infoLayerTopBarBack; title: boxTitle; titlePosition: point(24px, 17px); + titleWithSubtitlePosition: point(16px, 9px); + subtitlePosition: point(16px, 30px); bg: boxBg; mediaCancel: infoLayerTopBarMediaCancel; mediaActionsSkip: 6px; @@ -398,6 +412,7 @@ infoIconMediaGroup: icon {{ "info/info_common_groups", infoIconFg }}; infoIconMediaChannel: icon {{ "menu/channel", infoIconFg, point(4px, 4px) }}; infoIconMediaVoice: icon {{ "info/info_media_voice", infoIconFg }}; infoIconMediaStories: icon {{ "info/info_media_stories", infoIconFg }}; +infoIconMediaSaved: icon {{ "info/info_media_saved", infoIconFg }}; infoIconMediaStoriesArchive: icon {{ "info/info_stories_archive", infoIconFg }}; infoIconMediaStoriesRecent: icon {{ "info/info_stories_recent", infoIconFg }}; diff --git a/Telegram/SourceFiles/info/info_content_widget.h b/Telegram/SourceFiles/info/info_content_widget.h index 5d53efee5..a7782d4f9 100644 --- a/Telegram/SourceFiles/info/info_content_widget.h +++ b/Telegram/SourceFiles/info/info_content_widget.h @@ -96,6 +96,9 @@ public: } [[nodiscard]] virtual rpl::producer title() = 0; + [[nodiscard]] virtual rpl::producer subtitle() { + return nullptr; + } [[nodiscard]] virtual auto titleStories() -> rpl::producer; diff --git a/Telegram/SourceFiles/info/info_controller.h b/Telegram/SourceFiles/info/info_controller.h index 046635020..bb5eca6bf 100644 --- a/Telegram/SourceFiles/info/info_controller.h +++ b/Telegram/SourceFiles/info/info_controller.h @@ -126,6 +126,7 @@ public: Media, CommonGroups, SimilarChannels, + SavedSublists, Members, Settings, Downloads, diff --git a/Telegram/SourceFiles/info/info_memento.cpp b/Telegram/SourceFiles/info/info_memento.cpp index b2f78b029..ec4eb246e 100644 --- a/Telegram/SourceFiles/info/info_memento.cpp +++ b/Telegram/SourceFiles/info/info_memento.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/media/info_media_widget.h" #include "info/members/info_members_widget.h" #include "info/common_groups/info_common_groups_widget.h" +#include "info/saved/info_saved_sublists_widget.h" #include "info/settings/info_settings_widget.h" #include "info/similar_channels/info_similar_channels_widget.h" #include "info/polls/info_polls_results_widget.h" @@ -111,7 +112,9 @@ std::vector> Memento::DefaultStack( } Section Memento::DefaultSection(not_null peer) { - if (peer->sharedMediaInfo()) { + if (peer->savedSublistsInfo()) { + return Section(Section::Type::SavedSublists); + } else if (peer->sharedMediaInfo()) { return Section(Section::MediaType::Photo); } return Section(Section::Type::Profile); @@ -145,6 +148,8 @@ std::shared_ptr Memento::DefaultContent( case Section::Type::SimilarChannels: return std::make_shared( peer->asChannel()); + case Section::Type::SavedSublists: + return std::make_shared(&peer->session()); case Section::Type::Members: return std::make_shared( peer, diff --git a/Telegram/SourceFiles/info/info_top_bar.cpp b/Telegram/SourceFiles/info/info_top_bar.cpp index 83e00e16c..6aba07ad9 100644 --- a/Telegram/SourceFiles/info/info_top_bar.cpp +++ b/Telegram/SourceFiles/info/info_top_bar.cpp @@ -86,13 +86,36 @@ void TopBar::registerToggleControlCallback( }); } -void TopBar::setTitle(rpl::producer &&title) { +void TopBar::setTitle(TitleDescriptor descriptor) { if (_title) { delete _title; } + if (_subtitle) { + delete _subtitle; + } + const auto withSubtitle = !!descriptor.subtitle; + if (withSubtitle) { + _subtitle = Ui::CreateChild>( + this, + object_ptr( + this, + std::move(descriptor.subtitle), + _st.subtitle), + st::infoTopBarScale); + _subtitle->setDuration(st::infoTopBarDuration); + _subtitle->toggle( + !selectionMode() && !storiesTitle(), + anim::type::instant); + registerToggleControlCallback(_subtitle.data(), [=] { + return !selectionMode() && !storiesTitle() && !searchMode(); + }); + } _title = Ui::CreateChild>( this, - object_ptr(this, std::move(title), _st.title), + object_ptr( + this, + std::move(descriptor.title), + withSubtitle ? _st.titleWithSubtitle : _st.title), st::infoTopBarScale); _title->setDuration(st::infoTopBarDuration); _title->toggle( @@ -104,6 +127,9 @@ void TopBar::setTitle(rpl::producer &&title) { if (_back) { _title->setAttribute(Qt::WA_TransparentForMouseEvents); + if (_subtitle) { + _subtitle->setAttribute(Qt::WA_TransparentForMouseEvents); + } } updateControlsGeometry(width()); } @@ -128,6 +154,9 @@ void TopBar::enableBackButton() { if (_title) { _title->setAttribute(Qt::WA_TransparentForMouseEvents); } + if (_subtitle) { + _subtitle->setAttribute(Qt::WA_TransparentForMouseEvents); + } if (_storiesWrap) { _storiesWrap->raise(); } @@ -343,10 +372,21 @@ void TopBar::updateDefaultControlsGeometry(int newWidth) { newWidth); } if (_title) { - _title->moveToLeft( - _back ? _st.back.width : _st.titlePosition.x(), - _st.titlePosition.y(), - newWidth); + const auto x = _back + ? _st.back.width + : _subtitle + ? _st.titleWithSubtitlePosition.x() + : _st.titlePosition.x(); + const auto y = _subtitle + ? _st.titleWithSubtitlePosition.y() + : _st.titlePosition.y(); + _title->moveToLeft(x, y, newWidth); + if (_subtitle) { + _subtitle->moveToLeft( + _back ? _st.back.width : _st.subtitlePosition.x(), + _st.subtitlePosition.y(), + newWidth); + } } } diff --git a/Telegram/SourceFiles/info/info_top_bar.h b/Telegram/SourceFiles/info/info_top_bar.h index 496166954..9e6ad80a8 100644 --- a/Telegram/SourceFiles/info/info_top_bar.h +++ b/Telegram/SourceFiles/info/info_top_bar.h @@ -41,6 +41,11 @@ namespace Info { class Key; class Section; +struct TitleDescriptor { + rpl::producer title; + rpl::producer subtitle; +}; + class TopBar : public Ui::RpWidget { public: TopBar( @@ -56,7 +61,7 @@ public: return _storyClicks.events(); } - void setTitle(rpl::producer &&title); + void setTitle(TitleDescriptor descriptor); void setStories(rpl::producer content); void setStoriesArchive(bool archive); void enableBackButton(); @@ -155,6 +160,7 @@ private: QPointer> _back; std::vector> _buttons; QPointer> _title; + QPointer> _subtitle; bool _searchModeEnabled = false; bool _searchModeAvailable = false; diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index 02d1f75e1..32bd69c7a 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -188,23 +188,32 @@ void WrapWidget::injectActivePeerProfile(not_null peer) { ? _historyStack.front().section->section().type() : _controller->section().type(); const auto firstSectionMediaType = [&] { - if (firstSectionType == Section::Type::Profile) { + if (firstSectionType == Section::Type::Profile + || firstSectionType == Section::Type::SavedSublists) { return Section::MediaType::kCount; } return hasStackHistory() ? _historyStack.front().section->section().mediaType() : _controller->section().mediaType(); }(); - const auto expectedType = peer->sharedMediaInfo() + const auto savedSublistsInfo = peer->savedSublistsInfo(); + const auto sharedMediaInfo = peer->sharedMediaInfo(); + const auto expectedType = savedSublistsInfo + ? Section::Type::SavedSublists + : sharedMediaInfo ? Section::Type::Media : Section::Type::Profile; - const auto expectedMediaType = peer->sharedMediaInfo() + const auto expectedMediaType = savedSublistsInfo + ? Section::MediaType::kCount + : sharedMediaInfo ? Section::MediaType::Photo : Section::MediaType::kCount; if (firstSectionType != expectedType || firstSectionMediaType != expectedMediaType || firstPeer != peer) { - auto section = peer->sharedMediaInfo() + auto section = savedSublistsInfo + ? Section(Section::Type::SavedSublists) + : sharedMediaInfo ? Section(Section::MediaType::Photo) : Section(Section::Type::Profile); injectActiveProfileMemento(std::move( @@ -545,6 +554,8 @@ void WrapWidget::removeFromStack(const std::vector
§ions) { const auto &s = item.section->section(); if (s.type() != section.type()) { return false; + } else if (s.type() == Section::Type::SavedSublists) { + return true; } else if (s.type() == Section::Type::Media) { return (s.mediaType() == section.mediaType()); } else if (s.type() == Section::Type::Settings) { @@ -586,7 +597,10 @@ void WrapWidget::finishShowContent() { updateContentGeometry(); _content->setIsStackBottom(!hasStackHistory()); if (_topBar) { - _topBar->setTitle(_content->title()); + _topBar->setTitle({ + .title = _content->title(), + .subtitle = _content->subtitle(), + }); _topBar->setStories(_content->titleStories()); _topBar->setStoriesArchive( _controller->key().storiesTab() == Stories::Tab::Archive); diff --git a/Telegram/SourceFiles/info/media/info_media_buttons.h b/Telegram/SourceFiles/info/media/info_media_buttons.h index 527f7a4db..1331ae07d 100644 --- a/Telegram/SourceFiles/info/media/info_media_buttons.h +++ b/Telegram/SourceFiles/info/media/info_media_buttons.h @@ -10,8 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include "lang/lang_keys.h" +#include "data/data_saved_messages.h" +#include "data/data_session.h" #include "data/data_stories_ids.h" #include "storage/storage_shared_media.h" +#include "history/view/history_view_sublist_section.h" #include "info/info_memento.h" #include "info/info_controller.h" #include "info/profile/info_profile_values.h" @@ -126,7 +129,7 @@ inline auto AddCommonGroupsButton( Section::Type::CommonGroups)); }); return result; -}; +} inline auto AddSimilarChannelsButton( Ui::VerticalLayout *parent, @@ -150,7 +153,7 @@ inline auto AddSimilarChannelsButton( Section::Type::SimilarChannels)); }); return result; -}; +} inline auto AddStoriesButton( Ui::VerticalLayout *parent, @@ -178,6 +181,26 @@ inline auto AddStoriesButton( navigation->showSection(Info::Stories::Make(peer)); }); return result; -}; +} + +inline auto AddSavedSublistButton( + Ui::VerticalLayout *parent, + not_null navigation, + not_null peer, + Ui::MultiSlideTracker &tracker) { + auto result = AddCountedButton( + parent, + Profile::SavedSublistCountValue(peer), + [](int count) { + return tr::lng_profile_saved_messages(tr::now, lt_count, count); + }, + tracker)->entity(); + result->addClickHandler([=] { + navigation->showSection( + std::make_shared( + peer->owner().savedMessages().sublist(peer))); + }); + return result; +} } // namespace Info::Media diff --git a/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp b/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp index dce97363c..ee9eb4dfd 100644 --- a/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp @@ -43,7 +43,7 @@ InnerWidget::InnerWidget( // Allows showing additional shared media links and tabs. // Used for shared media in Saved Messages. void InnerWidget::setupOtherTypes() { - if (_controller->key().peer()->isSelf() && _isStackBottom) { + if (_controller->key().peer()->sharedMediaInfo() && _isStackBottom) { createOtherTypes(); } else { _otherTypes.destroy(); diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp index 16e63613d..610fafe38 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp @@ -225,8 +225,22 @@ object_ptr InnerWidget::setupSharedMedia( icon, st::infoSharedMediaButtonIconPosition); }; + auto addSavedSublistButton = [&]( + not_null peer, + const style::icon &icon) { + auto result = Media::AddSavedSublistButton( + content, + _controller, + peer, + tracker); + object_ptr( + result, + icon, + st::infoSharedMediaButtonIconPosition); + }; addStoriesButton(_peer, st::infoIconMediaStories); + addSavedSublistButton(_peer, st::infoIconMediaSaved); addMediaButton(MediaType::Photo, st::infoIconMediaPhoto); addMediaButton(MediaType::Video, st::infoIconMediaVideo); addMediaButton(MediaType::File, st::infoIconMediaFile); diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp index 3ea355cff..6c7b4ac2b 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp @@ -20,6 +20,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "data/notify/data_notify_settings.h" #include "data/data_peer_values.h" +#include "data/data_saved_messages.h" +#include "data/data_saved_sublist.h" #include "data/data_shared_media.h" #include "data/data_message_reactions.h" #include "data/data_folder.h" @@ -206,7 +208,7 @@ TextWithEntities AboutWithEntities( const auto used = (!user || isPremium || value.size() <= limit) ? value : value.mid(0, limit) + "..."; - auto result = TextWithEntities{ value }; + auto result = TextWithEntities{ used }; TextUtilities::ParseEntities(result, flags); if (stripExternal) { StripExternalLinks(result); @@ -536,6 +538,17 @@ rpl::producer SimilarChannelsCountValue( }); } +rpl::producer SavedSublistCountValue( + not_null peer) { + const auto saved = &peer->owner().savedMessages(); + const auto sublist = saved->sublist(peer); + if (!sublist->fullCount()) { + saved->loadMore(sublist); + return rpl::single(0) | rpl::then(sublist->fullCountValue()); + } + return sublist->fullCountValue(); +} + rpl::producer CanAddMemberValue(not_null peer) { if (const auto chat = peer->asChat()) { return peer->session().changes().peerFlagsValue( diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.h b/Telegram/SourceFiles/info/profile/info_profile_values.h index f669a4535..a74f22a42 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.h +++ b/Telegram/SourceFiles/info/profile/info_profile_values.h @@ -104,6 +104,8 @@ rpl::producer> MigratedOrMeValue( not_null user); [[nodiscard]] rpl::producer SimilarChannelsCountValue( not_null channel); +[[nodiscard]] rpl::producer SavedSublistCountValue( + not_null peer); [[nodiscard]] rpl::producer CanAddMemberValue( not_null peer); [[nodiscard]] rpl::producer FullReactionsCountValue( diff --git a/Telegram/SourceFiles/info/saved/info_saved_sublists_widget.cpp b/Telegram/SourceFiles/info/saved/info_saved_sublists_widget.cpp new file mode 100644 index 000000000..db3990963 --- /dev/null +++ b/Telegram/SourceFiles/info/saved/info_saved_sublists_widget.cpp @@ -0,0 +1,183 @@ +/* +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 "info/saved/info_saved_sublists_widget.h" +// +#include "data/data_saved_messages.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "dialogs/dialogs_inner_widget.h" +#include "history/view/history_view_sublist_section.h" +#include "info/media/info_media_buttons.h" +#include "info/profile/info_profile_icon.h" +#include "info/info_controller.h" +#include "info/info_memento.h" +#include "main/main_session.h" +#include "lang/lang_keys.h" +#include "ui/widgets/box_content_divider.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "styles/style_info.h" + +namespace Info::Saved { + +SublistsMemento::SublistsMemento(not_null session) +: ContentMemento(session->user(), nullptr, PeerId()) { +} + +Section SublistsMemento::section() const { + return Section(Section::Type::SavedSublists); +} + +object_ptr SublistsMemento::createWidget( + QWidget *parent, + not_null controller, + const QRect &geometry) { + auto result = object_ptr(parent, controller); + result->setInternalState(geometry, this); + return result; +} + +SublistsMemento::~SublistsMemento() = default; + +SublistsWidget::SublistsWidget( + QWidget *parent, + not_null controller) +: ContentWidget(parent, controller) +, _layout(setInnerWidget(object_ptr(this))) { + setupOtherTypes(); + + _list = _layout->add(object_ptr( + this, + controller->parentController(), + rpl::single(Dialogs::InnerWidget::ChildListShown()))); + _list->showSavedSublists(); + _list->setNarrowRatio(0.); + + _list->chosenRow() | rpl::start_with_next([=](Dialogs::ChosenRow row) { + if (const auto sublist = row.key.sublist()) { + controller->showSection( + std::make_shared(sublist), + Window::SectionShow::Way::Forward); + } + }, _list->lifetime()); + + const auto saved = &controller->session().data().savedMessages(); + _list->heightValue() | rpl::start_with_next([=] { + if (!saved->supported()) { + crl::on_main(controller, [=] { + controller->showSection( + Memento::Default(controller->session().user()), + Window::SectionShow::Way::Backward); + }); + } + }, lifetime()); + + _list->setLoadMoreCallback([=] { + saved->loadMore(); + }); +} + +void SublistsWidget::setupOtherTypes() { + auto wrap = _layout->add( + object_ptr>( + _layout, + object_ptr(_layout))); + auto content = wrap->entity(); + content->add(object_ptr( + content, + st::infoProfileSkip)); + + using Type = Media::Type; + auto tracker = Ui::MultiSlideTracker(); + const auto peer = controller()->session().user(); + const auto addMediaButton = [&]( + Type buttonType, + const style::icon &icon) { + auto result = Media::AddButton( + content, + controller(), + peer, + MsgId(), // topicRootId + nullptr, // migrated + buttonType, + tracker); + object_ptr( + result, + icon, + st::infoSharedMediaButtonIconPosition)->show(); + }; + + addMediaButton(Type::Photo, st::infoIconMediaPhoto); + addMediaButton(Type::Video, st::infoIconMediaVideo); + addMediaButton(Type::File, st::infoIconMediaFile); + addMediaButton(Type::MusicFile, st::infoIconMediaAudio); + addMediaButton(Type::Link, st::infoIconMediaLink); + addMediaButton(Type::RoundVoiceFile, st::infoIconMediaVoice); + addMediaButton(Type::GIF, st::infoIconMediaGif); + + content->add(object_ptr( + content, + st::infoProfileSkip)); + wrap->toggleOn(tracker.atLeastOneShownValue()); + wrap->finishAnimating(); + + _layout->add(object_ptr(_layout)); + _layout->add(object_ptr( + content, + st::infoProfileSkip)); +} + +rpl::producer SublistsWidget::title() { + return tr::lng_saved_messages(); +} + +rpl::producer SublistsWidget::subtitle() { + const auto saved = &controller()->session().data().savedMessages(); + return saved->chatsList()->fullSize().value( + ) | rpl::map([=](int value) { + return (value || saved->chatsList()->loaded()) + ? tr::lng_filters_chats_count(tr::now, lt_count, value) + : tr::lng_contacts_loading(tr::now); + }); +} + +bool SublistsWidget::showInternal(not_null memento) { + if (!controller()->validateMementoPeer(memento)) { + return false; + } + if (auto my = dynamic_cast(memento.get())) { + restoreState(my); + return true; + } + return false; +} + +void SublistsWidget::setInternalState( + const QRect &geometry, + not_null memento) { + setGeometry(geometry); + Ui::SendPendingMoveResizeEvents(this); + restoreState(memento); +} + +std::shared_ptr SublistsWidget::doCreateMemento() { + auto result = std::make_shared( + &controller()->session()); + saveState(result.get()); + return result; +} + +void SublistsWidget::saveState(not_null memento) { + memento->setScrollTop(scrollTopSave()); +} + +void SublistsWidget::restoreState(not_null memento) { + scrollTopRestore(memento->scrollTop()); +} + +} // namespace Info::Saved diff --git a/Telegram/SourceFiles/info/saved/info_saved_sublists_widget.h b/Telegram/SourceFiles/info/saved/info_saved_sublists_widget.h new file mode 100644 index 000000000..64ada22ab --- /dev/null +++ b/Telegram/SourceFiles/info/saved/info_saved_sublists_widget.h @@ -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 +*/ +#pragma once + +#include "info/info_content_widget.h" + +namespace Dialogs { +class InnerWidget; +} // namespace Dialogs + +namespace Main { +class Session; +} // namespace Main + +namespace Ui { +class VerticalLayout; +} // namespace Ui + +namespace Info::Saved { + +class SublistsMemento final : public ContentMemento { +public: + explicit SublistsMemento(not_null session); + + object_ptr createWidget( + QWidget *parent, + not_null controller, + const QRect &geometry) override; + + Section section() const override; + + ~SublistsMemento(); + +private: + +}; + +class SublistsWidget final : public ContentWidget { +public: + SublistsWidget( + QWidget *parent, + not_null controller); + + bool showInternal( + not_null memento) override; + + void setInternalState( + const QRect &geometry, + not_null memento); + + rpl::producer title() override; + rpl::producer subtitle() override; + +private: + void saveState(not_null memento); + void restoreState(not_null memento); + + std::shared_ptr doCreateMemento() override; + + void setupOtherTypes(); + + const not_null _layout; + Dialogs::InnerWidget *_list = nullptr; + +}; + +} // namespace Info::Saved diff --git a/Telegram/SourceFiles/info/similar_channels/info_similar_channels_widget.cpp b/Telegram/SourceFiles/info/similar_channels/info_similar_channels_widget.cpp index 886a7869e..cfc911aaf 100644 --- a/Telegram/SourceFiles/info/similar_channels/info_similar_channels_widget.cpp +++ b/Telegram/SourceFiles/info/similar_channels/info_similar_channels_widget.cpp @@ -513,4 +513,3 @@ void Widget::restoreState(not_null memento) { } } // namespace Info::SimilarChannels - diff --git a/Telegram/SourceFiles/info/similar_channels/info_similar_channels_widget.h b/Telegram/SourceFiles/info/similar_channels/info_similar_channels_widget.h index 7c32a855c..74f15e64f 100644 --- a/Telegram/SourceFiles/info/similar_channels/info_similar_channels_widget.h +++ b/Telegram/SourceFiles/info/similar_channels/info_similar_channels_widget.h @@ -68,4 +68,3 @@ private: }; } // namespace Info::SimilarChannels - diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp index d1d1cafad..2f5242e85 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp @@ -213,7 +213,7 @@ QString ItemBase::getResultThumbLetter() const { domain = parts.at(2); } - parts = domain.split('@').back().split('.'); + parts = domain.split('@').constLast().split('.'); if (parts.size() > 1) { return parts.at(parts.size() - 2).at(0).toUpper(); } diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp index a2f342d28..a8135979b 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp @@ -84,7 +84,6 @@ SendDataCommon::SentMessageFields SendGeo::getSentMessageFields() const { } SendDataCommon::SentMessageFields SendVenue::getSentMessageFields() const { - const auto venueType = QString(); return { .media = MTP_messageMediaVenue( _location.toMTP(), MTP_string(_title), diff --git a/Telegram/SourceFiles/intro/intro_phone.cpp b/Telegram/SourceFiles/intro/intro_phone.cpp index bf8a355bc..7ac420956 100644 --- a/Telegram/SourceFiles/intro/intro_phone.cpp +++ b/Telegram/SourceFiles/intro/intro_phone.cpp @@ -169,7 +169,8 @@ void PhoneWidget::submit() { // Check if such account is authorized already. const auto digitsOnly = [](QString value) { - return value.replace(QRegularExpression("[^0-9]"), QString()); + static const auto RegExp = QRegularExpression("[^0-9]"); + return value.replace(RegExp, QString()); }; const auto phoneDigits = digitsOnly(phone); for (const auto &[index, existing] : Core::App().domain().accounts()) { diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index 12bac051a..ca5298972 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -254,7 +254,7 @@ bool Session::premiumPossible() const { return true; } - return premium() || _premiumPossible.current(); + return premium() || premiumCanBuy(); } bool Session::premiumBadgesShown() const { @@ -283,6 +283,10 @@ rpl::producer Session::premiumPossibleValue() const { _1 || _2); } +bool Session::premiumCanBuy() const { + return _premiumPossible.current(); +} + bool Session::isTestMode() const { return mtp().isTestMode(); } diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h index 24c165f0b..cca061d08 100644 --- a/Telegram/SourceFiles/main/main_session.h +++ b/Telegram/SourceFiles/main/main_session.h @@ -87,6 +87,7 @@ public: [[nodiscard]] bool premiumPossible() const; [[nodiscard]] rpl::producer premiumPossibleValue() const; [[nodiscard]] bool premiumBadgesShown() const; + [[nodiscard]] bool premiumCanBuy() const; [[nodiscard]] bool isTestMode() const; [[nodiscard]] uint64 uniqueId() const; // userId() with TestDC shift. diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 143a347d1..7e3f7828f 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -752,13 +752,20 @@ void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat, UserD void MainWidget::handleAudioUpdate(const Media::Player::TrackState &state) { using State = Media::Player::State; const auto document = state.id.audio(); + const auto item = session().data().message(state.id.contextId()); if (!Media::Player::IsStoppedOrStopping(state.state)) { - createPlayer(); + const auto ttlSeconds = item + && !item->out() + && item->media() + && item->media()->ttlSeconds(); + if (!ttlSeconds) { + createPlayer(); + } } else if (state.state == State::StoppedAtStart) { Media::Player::instance()->stopAndClose(); } - if (const auto item = session().data().message(state.id.contextId())) { + if (item) { session().data().requestItemRepaint(item); } if (document) { diff --git a/Telegram/SourceFiles/media/audio/media_audio.cpp b/Telegram/SourceFiles/media/audio/media_audio.cpp index 3f4671749..c882856f6 100644 --- a/Telegram/SourceFiles/media/audio/media_audio.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio.cpp @@ -36,7 +36,6 @@ namespace { constexpr auto kSuppressRatioAll = 0.2; constexpr auto kSuppressRatioSong = 0.05; constexpr auto kWaveformCounterBufferSize = 256 * 1024; -constexpr auto kEffectDestructionDelay = crl::time(1000); QMutex AudioMutex; ALCdevice *AudioDevice = nullptr; @@ -538,16 +537,16 @@ Mixer::Mixer(not_null instance) }); }, _lifetime); - connect(this, SIGNAL(loaderOnStart(const AudioMsgId&, qint64)), _loader, SLOT(onStart(const AudioMsgId&, qint64))); - connect(this, SIGNAL(loaderOnCancel(const AudioMsgId&)), _loader, SLOT(onCancel(const AudioMsgId&)), Qt::QueuedConnection); + connect(this, SIGNAL(loaderOnStart(AudioMsgId,qint64)), _loader, SLOT(onStart(AudioMsgId,qint64))); + connect(this, SIGNAL(loaderOnCancel(AudioMsgId)), _loader, SLOT(onCancel(AudioMsgId)), Qt::QueuedConnection); connect(_loader, SIGNAL(needToCheck()), _fader, SLOT(onTimer())); - connect(_loader, SIGNAL(error(const AudioMsgId&)), this, SLOT(onError(const AudioMsgId&))); - connect(_fader, SIGNAL(needToPreload(const AudioMsgId&)), _loader, SLOT(onLoad(const AudioMsgId&))); - connect(_fader, SIGNAL(playPositionUpdated(const AudioMsgId&)), this, SIGNAL(updated(const AudioMsgId&))); - connect(_fader, SIGNAL(audioStopped(const AudioMsgId&)), this, SLOT(onStopped(const AudioMsgId&))); - connect(_fader, SIGNAL(error(const AudioMsgId&)), this, SLOT(onError(const AudioMsgId&))); - connect(this, SIGNAL(stoppedOnError(const AudioMsgId&)), this, SIGNAL(updated(const AudioMsgId&)), Qt::QueuedConnection); - connect(this, SIGNAL(updated(const AudioMsgId&)), this, SLOT(onUpdated(const AudioMsgId&))); + connect(_loader, SIGNAL(error(AudioMsgId)), this, SLOT(onError(AudioMsgId))); + connect(_fader, SIGNAL(needToPreload(AudioMsgId)), _loader, SLOT(onLoad(AudioMsgId))); + connect(_fader, SIGNAL(playPositionUpdated(AudioMsgId)), this, SIGNAL(updated(AudioMsgId))); + connect(_fader, SIGNAL(audioStopped(AudioMsgId)), this, SLOT(onStopped(AudioMsgId))); + connect(_fader, SIGNAL(error(AudioMsgId)), this, SLOT(onError(AudioMsgId))); + connect(this, SIGNAL(stoppedOnError(AudioMsgId)), this, SIGNAL(updated(AudioMsgId)), Qt::QueuedConnection); + connect(this, SIGNAL(updated(AudioMsgId)), this, SLOT(onUpdated(AudioMsgId))); _loaderThread.start(); _faderThread.start(); diff --git a/Telegram/SourceFiles/media/audio/media_audio_capture.h b/Telegram/SourceFiles/media/audio/media_audio_capture.h index 107274c0c..4bb23c2b0 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_capture.h +++ b/Telegram/SourceFiles/media/audio/media_audio_capture.h @@ -57,7 +57,7 @@ private: friend class Inner; bool _available = false; - rpl::variable _started = false;; + rpl::variable _started = false; rpl::event_stream _updates; QThread _thread; std::unique_ptr _inner; diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index 5e1655cc0..4dc687a27 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -503,6 +503,9 @@ bool Instance::moveInPlaylist( } const auto jumpByItem = [&](not_null item) { if (const auto media = item->media()) { + if (media->ttlSeconds()) { + return false; + } if (const auto document = media->document()) { if (autonext) { _switchToNext.fire({ diff --git a/Telegram/SourceFiles/media/player/media_player_panel.h b/Telegram/SourceFiles/media/player/media_player_panel.h index 17446fdc5..9123834ce 100644 --- a/Telegram/SourceFiles/media/player/media_player_panel.h +++ b/Telegram/SourceFiles/media/player/media_player_panel.h @@ -78,7 +78,7 @@ private: return width() - contentLeft() - contentRight(); } int contentHeight() const { - return height() - contentTop() - contentBottom();; + return height() - contentTop() - contentBottom(); } void startAnimation(); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index a5aeda804..0eab8e44d 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -564,7 +564,7 @@ OverlayWidget::OverlayWidget() || type == QEvent::TouchEnd || type == QEvent::TouchCancel) { if (handleTouchEvent(static_cast(e.get()))) { - return base::EventFilterResult::Cancel;; + return base::EventFilterResult::Cancel; } } else if (type == QEvent::Wheel) { handleWheelEvent(static_cast(e.get())); @@ -3061,7 +3061,7 @@ void OverlayWidget::refreshMediaViewer() { void OverlayWidget::refreshFromLabel() { if (_message) { _from = _message->originalSender(); - if (const auto info = _message->hiddenSenderInfo()) { + if (const auto info = _message->originalHiddenSenderInfo()) { _fromName = info->name; } else { Assert(_from != nullptr); @@ -4897,7 +4897,6 @@ void OverlayWidget::paintControls( const style::icon &icon; bool nonbright = false; }; - const QRect kEmpty; // When adding / removing controls please update RendererGL. const Control controls[] = { { diff --git a/Telegram/SourceFiles/mtproto/connection_http.cpp b/Telegram/SourceFiles/mtproto/connection_http.cpp index 37b9d2198..baaad903e 100644 --- a/Telegram/SourceFiles/mtproto/connection_http.cpp +++ b/Telegram/SourceFiles/mtproto/connection_http.cpp @@ -55,7 +55,8 @@ void HttpConnection::disconnectFromServer() { if (_status == Status::Finished) return; _status = Status::Finished; - for (const auto request : base::take(_requests)) { + const auto requests = base::take(_requests); + for (const auto request : requests) { request->abort(); request->deleteLater(); } @@ -85,8 +86,7 @@ void HttpConnection::connectToServer( if (Logs::DebugEnabled()) { _debugId = u"%1(dc:%2,%3)"_q .arg(_debugId.toInt()) - .arg(ProtocolDcDebugId(protocolDcId)) - .arg(url().toDisplayString()); + .arg(ProtocolDcDebugId(protocolDcId), url().toDisplayString()); } _pingTime = crl::now(); diff --git a/Telegram/SourceFiles/mtproto/connection_tcp.cpp b/Telegram/SourceFiles/mtproto/connection_tcp.cpp index 6fa51a999..26dff1dd9 100644 --- a/Telegram/SourceFiles/mtproto/connection_tcp.cpp +++ b/Telegram/SourceFiles/mtproto/connection_tcp.cpp @@ -536,9 +536,10 @@ void TcpConnection::connectToServer( const auto postfix = _socket->debugPostfix(); _debugId = u"%1(dc:%2,%3%4:%5%6)"_q .arg(_debugId.toInt()) - .arg(ProtocolDcDebugId(_protocolDcId)) - .arg((_proxy.type == ProxyData::Type::Mtproto) ? "mtproxy " : "") - .arg(_address) + .arg( + ProtocolDcDebugId(_protocolDcId), + (_proxy.type == ProxyData::Type::Mtproto) ? "mtproxy " : "", + _address) .arg(_port) .arg(postfix.isEmpty() ? _protocol->debugPostfix() : postfix); _socket->setDebugId(_debugId); diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp index f20bb78e0..77e4ce6ff 100644 --- a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp +++ b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp @@ -65,7 +65,7 @@ QByteArray DnsUserAgent() { static const auto kResult = QByteArray( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/118.0.5993.117 Safari/537.36"); + "Chrome/120.0.6099.109 Safari/537.36"); return kResult; } @@ -104,8 +104,9 @@ std::vector ParseDnsResponse( return {}; } + const auto array = (*answerIt).toArray(); auto result = std::vector(); - for (const auto elem : (*answerIt).toArray()) { + for (const auto elem : array) { if (!elem.isObject()) { LOG(("Config Error: Not an object found " "in Answer array in dns response JSON.")); diff --git a/Telegram/SourceFiles/mtproto/mtp_instance.cpp b/Telegram/SourceFiles/mtproto/mtp_instance.cpp index e5b5cc8ec..7a9908d99 100644 --- a/Telegram/SourceFiles/mtproto/mtp_instance.cpp +++ b/Telegram/SourceFiles/mtproto/mtp_instance.cpp @@ -1354,8 +1354,11 @@ bool Instance::Private::onErrorDefault( const auto &type = error.type(); const auto code = error.code(); auto badGuestDc = (code == 400) && (type == u"FILE_ID_INVALID"_q); + static const auto MigrateRegExp = QRegularExpression("^(FILE|PHONE|NETWORK|USER)_MIGRATE_(\\d+)$"); + static const auto FloodWaitRegExp = QRegularExpression("^FLOOD_WAIT_(\\d+)$"); + static const auto SlowmodeWaitRegExp = QRegularExpression("^SLOWMODE_WAIT_(\\d+)$"); QRegularExpressionMatch m1, m2; - if ((m1 = QRegularExpression("^(FILE|PHONE|NETWORK|USER)_MIGRATE_(\\d+)$").match(type)).hasMatch()) { + if ((m1 = MigrateRegExp.match(type)).hasMatch()) { if (!requestId) return false; auto dcWithShift = ShiftedDcId(0); @@ -1458,8 +1461,8 @@ bool Instance::Private::onErrorDefault( return true; } else if (code < 0 || code >= 500 - || (m1 = QRegularExpression("^FLOOD_WAIT_(\\d+)$").match(type)).hasMatch() - || ((m2 = QRegularExpression("^SLOWMODE_WAIT_(\\d+)$").match(type)).hasMatch() + || (m1 = FloodWaitRegExp.match(type)).hasMatch() + || ((m2 = SlowmodeWaitRegExp.match(type)).hasMatch() && m2.captured(1).toInt() < 3)) { if (!requestId) return false; diff --git a/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp b/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp index c659f8509..048845a8f 100644 --- a/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp +++ b/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp @@ -757,8 +757,9 @@ bool DcOptions::loadFromFile(const QString &path) { stream.setCodec("UTF-8"); #endif // Qt < 6.0.0 while (!stream.atEnd()) { + static const auto RegExp = QRegularExpression(R"(\s)"); auto line = stream.readLine(); - auto components = line.split(QRegularExpression(R"(\s)"), Qt::SkipEmptyParts); + auto components = line.split(RegExp, Qt::SkipEmptyParts); if (components.isEmpty() || components[0].startsWith('#')) { continue; } diff --git a/Telegram/SourceFiles/mtproto/mtproto_proxy_data.cpp b/Telegram/SourceFiles/mtproto/mtproto_proxy_data.cpp index 260fe0b12..8ed267514 100644 --- a/Telegram/SourceFiles/mtproto/mtproto_proxy_data.cpp +++ b/Telegram/SourceFiles/mtproto/mtproto_proxy_data.cpp @@ -157,11 +157,12 @@ bool ProxyData::supportsCalls() const { } bool ProxyData::tryCustomResolve() const { + static const auto RegExp = QRegularExpression( + QStringLiteral("^\\d+\\.\\d+\\.\\d+\\.\\d+$") + ); return (type == Type::Socks5 || type == Type::Mtproto) && !qthelp::is_ipv6(host) - && !QRegularExpression( - QStringLiteral("^\\d+\\.\\d+\\.\\d+\\.\\d+$") - ).match(host).hasMatch(); + && !RegExp.match(host).hasMatch(); } bytes::vector ProxyData::secretFromMtprotoPassword() const { diff --git a/Telegram/SourceFiles/mtproto/mtproto_response.cpp b/Telegram/SourceFiles/mtproto/mtproto_response.cpp index 0c0a71604..772904433 100644 --- a/Telegram/SourceFiles/mtproto/mtproto_response.cpp +++ b/Telegram/SourceFiles/mtproto/mtproto_response.cpp @@ -25,11 +25,11 @@ namespace { Error::Error(const MTPrpcError &error) : _code(error.c_rpc_error().verror_code().v) { QString text = qs(error.c_rpc_error().verror_message()); - const auto expression = QRegularExpression( + static const auto Expression = QRegularExpression( "^([A-Z0-9_]+)(: .*)?$", (QRegularExpression::DotMatchesEverythingOption | QRegularExpression::MultilineOption)); - const auto match = expression.match(text); + const auto match = Expression.match(text); if (match.hasMatch()) { _type = match.captured(1); _description = match.captured(2).mid(2); diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 6f173d87b..87ce36bbc 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -114,7 +114,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#38116ee0 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; +message#76bec211 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true id:int from_id:flags.8?Peer peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; @@ -122,7 +122,7 @@ messageMediaPhoto#695150d7 flags:# spoiler:flags.3?true photo:flags.0?Photo ttl_ messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia; messageMediaContact#70322949 phone_number:string first_name:string last_name:string vcard:string user_id:long = MessageMedia; messageMediaUnsupported#9f84f49e = MessageMedia; -messageMediaDocument#4cf4d72d flags:# nopremium:flags.3?true spoiler:flags.4?true document:flags.0?Document alt_document:flags.5?Document ttl_seconds:flags.2?int = MessageMedia; +messageMediaDocument#4cf4d72d flags:# nopremium:flags.3?true spoiler:flags.4?true video:flags.6?true round:flags.7?true voice:flags.8?true document:flags.0?Document alt_document:flags.5?Document ttl_seconds:flags.2?int = MessageMedia; messageMediaWebPage#ddf10c3b flags:# force_large_media:flags.0?true force_small_media:flags.1?true manual:flags.3?true safe:flags.4?true webpage:WebPage = MessageMedia; messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia; messageMediaGame#fdb19008 game:Game = MessageMedia; @@ -398,6 +398,8 @@ updateChannelViewForumAsMessages#7b68920 channel_id:long enabled:Bool = Update; updatePeerWallpaper#ae3f101d flags:# wallpaper_overridden:flags.1?true peer:Peer wallpaper:flags.0?WallPaper = Update; updateBotMessageReaction#ac21d3ce peer:Peer msg_id:int date:int actor:Peer old_reactions:Vector new_reactions:Vector qts:int = Update; updateBotMessageReactions#9cb7759 peer:Peer msg_id:int date:int reactions:Vector qts:int = Update; +updateSavedDialogPinned#aeaf9e74 flags:# pinned:flags.0?true peer:DialogPeer = Update; +updatePinnedSavedDialogs#686c85a6 flags:# order:flags.0?Vector = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -721,7 +723,7 @@ messages.botResults#e021f2f6 flags:# gallery:flags.0?true query_id:long next_off exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink; -messageFwdHeader#5f777dce flags:# imported:flags.7?true from_id:flags.0?Peer from_name:flags.5?string date:int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int psa_type:flags.6?string = MessageFwdHeader; +messageFwdHeader#4e4df4bb flags:# imported:flags.7?true saved_out:flags.11?true from_id:flags.0?Peer from_name:flags.5?string date:int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int saved_from_id:flags.8?Peer saved_from_name:flags.9?string saved_date:flags.10?int psa_type:flags.6?string = MessageFwdHeader; auth.codeTypeSms#72a3158c = auth.CodeType; auth.codeTypeCall#741cd3e3 = auth.CodeType; @@ -1635,6 +1637,12 @@ storyReactionPublicRepost#cfcd0f13 peer_id:Peer story:StoryItem = StoryReaction; stories.storyReactionsList#aa5f789c flags:# count:int reactions:Vector chats:Vector users:Vector next_offset:flags.0?string = stories.StoryReactionsList; +savedDialog#bd87cb6c flags:# pinned:flags.2?true peer:Peer top_message:int = SavedDialog; + +messages.savedDialogs#f83ae221 dialogs:Vector messages:Vector chats:Vector users:Vector = messages.SavedDialogs; +messages.savedDialogsSlice#44ba9dd9 count:int dialogs:Vector messages:Vector chats:Vector users:Vector = messages.SavedDialogs; +messages.savedDialogsNotModified#c01f6fe8 count:int = messages.SavedDialogs; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1794,7 +1802,7 @@ contacts.setBlocked#94c65c76 flags:# my_stories_from:flags.0?true id:Vector = messages.Messages; messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.Dialogs; messages.getHistory#4423e6c5 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; -messages.search#a0fda762 flags:# peer:InputPeer q:string from_id:flags.0?InputPeer top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; +messages.search#a7b4e929 flags:# peer:InputPeer q:string from_id:flags.0?InputPeer saved_peer_id:flags.2?InputPeer top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages; messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory; messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; @@ -1897,7 +1905,7 @@ messages.getEmojiKeywords#35a0e062 lang_code:string = EmojiKeywordsDifference; messages.getEmojiKeywordsDifference#1508b6af lang_code:string from_version:int = EmojiKeywordsDifference; messages.getEmojiKeywordsLanguages#4e9963b2 lang_codes:Vector = Vector; messages.getEmojiURL#d5b10c26 lang_code:string = EmojiURL; -messages.getSearchCounters#ae7cc1 flags:# peer:InputPeer top_msg_id:flags.0?int filters:Vector = Vector; +messages.getSearchCounters#1bbcf300 flags:# peer:InputPeer saved_peer_id:flags.2?InputPeer top_msg_id:flags.0?int filters:Vector = Vector; messages.requestUrlAuth#198fb446 flags:# peer:flags.1?InputPeer msg_id:flags.1?int button_id:flags.1?int url:flags.2?string = UrlAuthResult; messages.acceptUrlAuth#b12c7125 flags:# write_allowed:flags.0?true peer:flags.1?InputPeer msg_id:flags.1?int button_id:flags.1?int url:flags.2?string = UrlAuthResult; messages.hidePeerSettingsBar#4facb138 peer:InputPeer = Bool; @@ -1933,8 +1941,8 @@ messages.setHistoryTTL#b80e5fe4 peer:InputPeer period:int = Updates; messages.checkHistoryImportPeer#5dc60f03 peer:InputPeer = messages.CheckedHistoryImportPeer; messages.setChatTheme#e63be13f peer:InputPeer emoticon:string = Updates; messages.getMessageReadParticipants#31c1c44f peer:InputPeer msg_id:int = Vector; -messages.getSearchResultsCalendar#49f0bde9 peer:InputPeer filter:MessagesFilter offset_id:int offset_date:int = messages.SearchResultsCalendar; -messages.getSearchResultsPositions#6e9583a3 peer:InputPeer filter:MessagesFilter offset_id:int limit:int = messages.SearchResultsPositions; +messages.getSearchResultsCalendar#6aa3f6bd flags:# peer:InputPeer saved_peer_id:flags.2?InputPeer filter:MessagesFilter offset_id:int offset_date:int = messages.SearchResultsCalendar; +messages.getSearchResultsPositions#9c7f2f10 flags:# peer:InputPeer saved_peer_id:flags.2?InputPeer filter:MessagesFilter offset_id:int limit:int = messages.SearchResultsPositions; messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPeer user_id:InputUser = Updates; messages.hideAllChatJoinRequests#e085f4ea flags:# approved:flags.0?true peer:InputPeer link:flags.1?string = Updates; messages.toggleNoForwards#b11eafa2 peer:InputPeer enabled:Bool = Updates; @@ -1979,6 +1987,12 @@ messages.getBotApp#34fdc5c3 app:InputBotApp hash:long = messages.BotApp; messages.requestAppWebView#8c5a3b3c flags:# write_allowed:flags.0?true peer:InputPeer app:InputBotApp start_param:flags.1?string theme_params:flags.2?DataJSON platform:string = AppWebViewResult; messages.setChatWallPaper#8ffacae1 flags:# for_both:flags.3?true revert:flags.4?true peer:InputPeer wallpaper:flags.0?InputWallPaper settings:flags.2?WallPaperSettings id:flags.1?int = Updates; messages.searchEmojiStickerSets#92b4494c flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets; +messages.getSavedDialogs#5381d21a flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.SavedDialogs; +messages.getSavedHistory#3d9a414d peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; +messages.deleteSavedHistory#6e98102b flags:# peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory; +messages.getPinnedSavedDialogs#d63d94e0 = messages.SavedDialogs; +messages.toggleSavedDialogPin#ac81bbde flags:# pinned:flags.0?true peer:InputDialogPeer = Bool; +messages.reorderPinnedSavedDialogs#8b716587 flags:# force:flags.0?true order:Vector = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference; @@ -2221,4 +2235,4 @@ premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector peer:InputPeer = p premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus; premium.getUserBoosts#39854d1f peer:InputPeer user_id:InputUser = premium.BoostsList; -// LAYER 169 +// LAYER 170 diff --git a/Telegram/SourceFiles/mtproto/special_config_request.cpp b/Telegram/SourceFiles/mtproto/special_config_request.cpp index d0ca19187..973651811 100644 --- a/Telegram/SourceFiles/mtproto/special_config_request.cpp +++ b/Telegram/SourceFiles/mtproto/special_config_request.cpp @@ -62,8 +62,9 @@ QString InstanceId() { } bool CheckPhoneByPrefixesRules(const QString &phone, const QString &rules) { + static const auto RegExp = QRegularExpression("[^0-9]"); const auto check = QString(phone).replace( - QRegularExpression("[^0-9]"), + RegExp, QString()); auto result = false; for (const auto &prefix : rules.split(',')) { diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 26413eb00..a7eee4c19 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -1693,7 +1693,7 @@ Link::Link( domain = parts.at(2); } - parts = domain.split('@').back().split('.', Qt::SkipEmptyParts); + parts = domain.split('@').constLast().split('.', Qt::SkipEmptyParts); if (parts.size() > 1) { _letter = parts.at(parts.size() - 2).at(0).toUpper(); if (_title.isEmpty()) { diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp index ddfd24ce4..14ee9bd78 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp @@ -152,11 +152,12 @@ EditDocumentScheme GetDocumentScheme( }; using Result = std::optional; const auto NameValidate = [](const QString &value) -> Result { + static const auto RegExp = QRegularExpression( + "^[a-zA-Z0-9\\.,/&\\-' ]+$" + ); if (value.isEmpty() || value.size() > kMaxNameSize) { return QString(); - } else if (!QRegularExpression( - "^[a-zA-Z0-9\\.,/&\\-' ]+$" - ).match(value).hasMatch()) { + } else if (!RegExp.match(value).hasMatch()) { return tr::lng_passport_bad_name(tr::now); } return std::nullopt; @@ -167,14 +168,16 @@ EditDocumentScheme GetDocumentScheme( const auto StreetValidate = LimitedValidate(kMaxStreetSize); const auto CityValidate = LimitedValidate(kMaxCitySize, kMinCitySize); const auto PostcodeValidate = FromBoolean([](const QString &value) { - return QRegularExpression( + static const auto RegExp = QRegularExpression( QString("^[a-zA-Z0-9\\-]{2,%1}$").arg(kMaxPostcodeSize) - ).match(value).hasMatch(); + ); + return RegExp.match(value).hasMatch(); }); const auto DateValidateBoolean = [](const QString &value) { - return QRegularExpression( + static const auto RegExp = QRegularExpression( "^\\d{2}\\.\\d{2}\\.\\d{4}$" - ).match(value).hasMatch(); + ); + return RegExp.match(value).hasMatch(); }; const auto DateValidate = FromBoolean(DateValidateBoolean); const auto DateOrEmptyValidate = FromBoolean([=](const QString &value) { @@ -479,9 +482,8 @@ EditContactScheme GetContactScheme(Scope::Type type) { result.newHeader = tr::lng_passport_new_phone(tr::now); result.aboutNew = tr::lng_passport_new_phone_code(tr::now); result.validate = [](const QString &value) { - return QRegularExpression( - "^\\d{2,12}$" - ).match(value).hasMatch(); + static const auto RegExp = QRegularExpression("^\\d{2,12}$"); + return RegExp.match(value).hasMatch(); }; result.format = [](const QString &value) { return Ui::FormatPhone(value); diff --git a/Telegram/SourceFiles/passport/ui/passport_details_row.cpp b/Telegram/SourceFiles/passport/ui/passport_details_row.cpp index 2fc7c3698..b50ff5164 100644 --- a/Telegram/SourceFiles/passport/ui/passport_details_row.cpp +++ b/Telegram/SourceFiles/passport/ui/passport_details_row.cpp @@ -51,7 +51,8 @@ PostcodeInput::PostcodeInput( rpl::producer placeholder, const QString &val) : MaskedInputField(parent, st, std::move(placeholder), val) { - if (!QRegularExpression("^[a-zA-Z0-9\\-]+$").match(val).hasMatch()) { + static const auto RegExp = QRegularExpression("^[a-zA-Z0-9\\-]+$"); + if (!RegExp.match(val).hasMatch()) { setText(QString()); } } @@ -414,8 +415,9 @@ void CountryRow::chooseCountry() { } QDate ValidateDate(const QString &value) { - const auto match = QRegularExpression( - "^([0-9]{2})\\.([0-9]{2})\\.([0-9]{4})$").match(value); + static const auto RegExp = QRegularExpression( + "^([0-9]{2})\\.([0-9]{2})\\.([0-9]{4})$"); + const auto match = RegExp.match(value); if (!match.hasMatch()) { return QDate(); } diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 9ff737d1c..9ca9bc668 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -985,8 +985,7 @@ void Form::validateCard( if (error) { LOG(("Stripe Error %1: %2 (%3)" ).arg(int(error.code()) - ).arg(error.description() - ).arg(error.message())); + ).arg(error.description(), error.message())); _updates.fire(Error{ Error::Type::Stripe, error.description() }); } else { setPaymentCredentials({ @@ -1036,8 +1035,7 @@ void Form::validateCard( if (error) { LOG(("SmartGlocal Error %1: %2 (%3)" ).arg(int(error.code()) - ).arg(error.description() - ).arg(error.message())); + ).arg(error.description(), error.message())); _updates.fire(Error{ Error::Type::SmartGlocal, error.description(), diff --git a/Telegram/SourceFiles/payments/smartglocal/smartglocal_card.cpp b/Telegram/SourceFiles/payments/smartglocal/smartglocal_card.cpp index 0423216ec..a08b0980d 100644 --- a/Telegram/SourceFiles/payments/smartglocal/smartglocal_card.cpp +++ b/Telegram/SourceFiles/payments/smartglocal/smartglocal_card.cpp @@ -54,8 +54,9 @@ bool Card::empty() const { } QString Last4(const Card &card) { + static const auto RegExp = QRegularExpression("[^\\d]\\d*(\\d{4})$"); const auto masked = card.maskedNumber(); - const auto m = QRegularExpression("[^\\d]\\d*(\\d{4})$").match(masked); + const auto m = RegExp.match(masked); return m.hasMatch() ? m.captured(1) : QString(); } diff --git a/Telegram/SourceFiles/payments/stripe/stripe_card_validator.cpp b/Telegram/SourceFiles/payments/stripe/stripe_card_validator.cpp index e6542adf0..599a3922d 100644 --- a/Telegram/SourceFiles/payments/stripe/stripe_card_validator.cpp +++ b/Telegram/SourceFiles/payments/stripe/stripe_card_validator.cpp @@ -90,11 +90,13 @@ struct BinRange { } [[nodiscard]] bool IsNumeric(const QString &value) { - return QRegularExpression("^[0-9]*$").match(value).hasMatch(); + static const auto RegExp = QRegularExpression("^[0-9]*$"); + return RegExp.match(value).hasMatch(); } [[nodiscard]] QString RemoveWhitespaces(QString value) { - return value.replace(QRegularExpression("\\s"), QString()); + static const auto RegExp = QRegularExpression("\\s"); + return value.replace(RegExp, QString()); } [[nodiscard]] std::vector BinRangesForNumber( @@ -233,7 +235,7 @@ ExpireDateValidationResult ValidateExpireDate( return { ValidationState::Incomplete }; } const auto normalized = (sanitized[0] > '1' ? "0" : "") + sanitized; - const auto month = normalized.mid(0, 2).toInt(); + const auto month = base::StringViewMid(normalized, 0, 2).toInt(); if (month < 1 || month > 12) { return { ValidationState::Invalid }; } else if (normalized.size() < 4) { @@ -241,7 +243,7 @@ ExpireDateValidationResult ValidateExpireDate( } else if (normalized.size() > 4) { return { ValidationState::Invalid }; } - const auto year = 2000 + normalized.mid(2).toInt(); + const auto year = 2000 + base::StringViewMid(normalized, 2).toInt(); const auto thresholdDate = overrideExpireDateThreshold.value_or( QDate::currentDate()); diff --git a/Telegram/SourceFiles/payments/ui/payments_edit_card.cpp b/Telegram/SourceFiles/payments/ui/payments_edit_card.cpp index 34efdc5cc..ec18ef6d2 100644 --- a/Telegram/SourceFiles/payments/ui/payments_edit_card.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_edit_card.cpp @@ -39,7 +39,8 @@ struct SimpleFieldState { } [[nodiscard]] QString RemoveNonNumbers(QString value) { - return value.replace(QRegularExpression("[^0-9]"), QString()); + static const auto RegExp = QRegularExpression("[^0-9]"); + return value.replace(RegExp, QString()); } [[nodiscard]] SimpleFieldState NumbersOnlyState(SimpleFieldState state) { diff --git a/Telegram/SourceFiles/payments/ui/payments_field.cpp b/Telegram/SourceFiles/payments/ui/payments_field.cpp index 73c87dff0..e182714e3 100644 --- a/Telegram/SourceFiles/payments/ui/payments_field.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_field.cpp @@ -36,7 +36,8 @@ struct SimpleFieldState { } [[nodiscard]] QString RemoveNonNumbers(QString value) { - return value.replace(QRegularExpression("[^0-9]"), QString()); + static const auto RegExp = QRegularExpression("[^0-9]"); + return value.replace(RegExp, QString()); } [[nodiscard]] SimpleFieldState CleanMoneyState( @@ -216,6 +217,7 @@ struct SimpleFieldState { const FieldConfig &config, const QString &parsed, const QString &countryIso2) { + static const auto RegExp = QRegularExpression("[^0-9]\\."); if (config.type == FieldType::Country) { return countryIso2; } else if (config.type == FieldType::Money) { @@ -227,16 +229,14 @@ struct SimpleFieldState { QChar(','), QChar('.') ).replace( - QRegularExpression("[^0-9\\.]"), + RegExp, QString() ).toDouble(); return QString::number( int64(base::SafeRound(real * std::pow(10., rule.exponent)))); } else if (config.type == FieldType::CardNumber || config.type == FieldType::CardCVC) { - return QString(parsed).replace( - QRegularExpression("[^0-9\\.]"), - QString()); + return QString(parsed).replace(RegExp, QString()); } return parsed; } diff --git a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp index a8f6c3d63..ae68d8bdd 100644 --- a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp @@ -388,7 +388,6 @@ void FormSummary::setupSuggestedTips(not_null layout) { st::paymentsTipButtonsPadding); const auto state = outer->lifetime().make_state(); for (const auto amount : _invoice.suggestedTips) { - const auto text = formatAmount(amount, true); const auto selected = (amount == _invoice.tipsSelected); const auto &st = selected ? _tipChosen diff --git a/Telegram/SourceFiles/platform/linux/integration_linux.cpp b/Telegram/SourceFiles/platform/linux/integration_linux.cpp index 6c461ec73..1087e09fb 100644 --- a/Telegram/SourceFiles/platform/linux/integration_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/integration_linux.cpp @@ -55,17 +55,17 @@ public: }); } - void open_(GFile **files, int n_files, const char*) noexcept override { - Core::Sandbox::Instance().customEnterFromEventLoop([&] { - for (int i = 0; i < n_files; ++i) { - QFileOpenEvent e( - QUrl(QString::fromUtf8(g_file_get_uri(files[i])))); - QGuiApplication::sendEvent(qApp, &e); - } - }); + void open_( + gi::Collection files, + const gi::cstring_v hint) noexcept override { + for (auto file : files) { + QFileOpenEvent e(QUrl(QString::fromStdString(file.get_uri()))); + QGuiApplication::sendEvent(qApp, &e); + } } - void add_platform_data_(GLib::VariantBuilder builder) noexcept override { + void add_platform_data_( + GLib::VariantBuilder_Ref builder) noexcept override { if (Platform::IsWayland()) { const auto token = qgetenv("XDG_ACTIVATION_TOKEN"); if (!token.isEmpty()) { @@ -113,8 +113,7 @@ Application::Application() Glib::create_variant( NotificationId().toTuple() ).get_type().gobj_copy(), - gi::transfer_full, - gi::direction_out + gi::transfer_full ); } catch (...) { return GLib::VariantType(); @@ -239,11 +238,7 @@ void LinuxIntegration::initInhibit() { return; } - auto uniqueName = _inhibitProxy - .get_connection() - .get_unique_name() - .value_or(""); - + std::string uniqueName = _inhibitProxy.get_connection().get_unique_name(); uniqueName.erase(0, 1); uniqueName.replace(uniqueName.find('.'), 1, 1, '_'); @@ -277,20 +272,18 @@ void LinuxIntegration::initInhibit() { ); }); - const auto options = std::array{ - GLib::Variant::new_dict_entry( - GLib::Variant::new_string("handle_token"), - GLib::Variant::new_variant( - GLib::Variant::new_string(handleToken))), - GLib::Variant::new_dict_entry( - GLib::Variant::new_string("session_handle_token"), - GLib::Variant::new_variant( - GLib::Variant::new_string(sessionHandleToken))), - }; - inhibit().call_create_monitor( - {}, - GLib::Variant::new_array(options.data(), options.size()), + "", + GLib::Variant::new_array({ + GLib::Variant::new_dict_entry( + GLib::Variant::new_string("handle_token"), + GLib::Variant::new_variant( + GLib::Variant::new_string(handleToken))), + GLib::Variant::new_dict_entry( + GLib::Variant::new_string("session_handle_token"), + GLib::Variant::new_variant( + GLib::Variant::new_string(sessionHandleToken))), + }), nullptr); } diff --git a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp index d7a6fe942..9afde3b19 100644 --- a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp @@ -111,14 +111,14 @@ bool Launcher::launchUpdater(UpdaterLaunch action) { return GLib::spawn_async( initialWorkingDir().toStdString(), argumentsList, - std::nullopt, + {}, GLib::SpawnFlags::FILE_AND_ARGV_ZERO_, nullptr, nullptr, nullptr); } else if (!GLib::spawn_sync( argumentsList, - std::nullopt, + {}, // if the spawn is sync, working directory is not set // and GLib::SpawnFlags::LEAVE_DESCRIPTORS_OPEN_ is set, // it goes through an optimized code path diff --git a/Telegram/SourceFiles/settings/settings_experimental.cpp b/Telegram/SourceFiles/settings/settings_experimental.cpp index 3b9a5a069..014987245 100644 --- a/Telegram/SourceFiles/settings/settings_experimental.cpp +++ b/Telegram/SourceFiles/settings/settings_experimental.cpp @@ -17,7 +17,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_style_radius.h" #include "base/options.h" #include "core/application.h" -#include "core/sandbox.h" #include "core/launcher.h" #include "chat_helpers/tabbed_panel.h" #include "dialogs/dialogs_widget.h" @@ -144,7 +143,6 @@ void SetupExperimental( addToggle(ChatHelpers::kOptionTabbedPanelShowOnClick); addToggle(Dialogs::kOptionForumHideChatsList); addToggle(Core::kOptionFractionalScalingEnabled); - addToggle(Core::kOptionForceWaylandFractionalScaling); addToggle(Window::kOptionViewProfileInChatsListContextMenu); addToggle(Info::Profile::kOptionShowPeerIdBelowAbout); addToggle(Ui::GL::kOptionAllowLinuxNvidiaOpenGL); diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index 1bcf70ae2..064f82fcd 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -432,7 +432,7 @@ void SetupPremium( controller->setPremiumRef("settings"); showOther(PremiumId()); }); - { + if (controller->session().premiumCanBuy()) { const auto button = AddButtonWithIcon( container, tr::lng_settings_gift_premium(), @@ -440,7 +440,7 @@ void SetupPremium( { .icon = &st::menuIconGiftPremium } ); button->addClickHandler([=] { - controller->showGiftPremiumsBox(); + controller->showGiftPremiumsBox(u"gift"_q); }); constexpr auto kNewExpiresAt = int(1735689600); if (base::unixtime::now() < kNewExpiresAt) { diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index a0335f537..cd4aee433 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -171,6 +171,7 @@ AdminLog::OwnedItem GenerateForwardedItem( MTP_int(0), // Not used (would've been trimmed to 32 bits). peerToMTP(history->peer->id), peerToMTP(history->peer->id), + MTPPeer(), // saved_peer_id MTP_messageFwdHeader( MTP_flags(MTPDmessageFwdHeader::Flag::f_from_id), peerToMTP(history->session().userPeerId()), @@ -180,6 +181,9 @@ AdminLog::OwnedItem GenerateForwardedItem( MTPstring(), // post_author MTPPeer(), // saved_from_peer MTPint(), // saved_from_msg_id + MTPPeer(), // saved_from_id + MTPstring(), // saved_from_name + MTPint(), // saved_date MTPstring()), // psa_type MTPlong(), // via_bot_id MTPMessageReplyHeader(), diff --git a/Telegram/SourceFiles/settings/settings_websites.cpp b/Telegram/SourceFiles/settings/settings_websites.cpp index a963a0488..bff91b155 100644 --- a/Telegram/SourceFiles/settings/settings_websites.cpp +++ b/Telegram/SourceFiles/settings/settings_websites.cpp @@ -32,7 +32,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { constexpr auto kShortPollTimeout = 60 * crl::time(1000); -constexpr auto kMaxDeviceModelLength = 32; using EntryData = Api::Websites::Entry; diff --git a/Telegram/SourceFiles/statistics/statistics_data_deserialize.cpp b/Telegram/SourceFiles/statistics/statistics_data_deserialize.cpp index 09fb3aa93..355c500c1 100644 --- a/Telegram/SourceFiles/statistics/statistics_data_deserialize.cpp +++ b/Telegram/SourceFiles/statistics/statistics_data_deserialize.cpp @@ -136,11 +136,11 @@ Data::StatisticalChart StatisticalChartFromJSON(const QByteArray &json) { const auto colors = root.value(u"colors"_q).toObject(); const auto names = root.value(u"names"_q).toObject(); - const auto colorPattern = u"(.*)(#.*)"_q; for (auto &line : result.lines) { const auto colorIt = colors.constFind(line.idString); if (colorIt != colors.constEnd() && (*colorIt).isString()) { - const auto match = QRegularExpression(colorPattern).match( + static const auto RegExp = QRegularExpression(u"(.*)(#.*)"_q); + const auto match = RegExp.match( colorIt->toString()); if (match.hasMatch()) { line.colorKey = match.captured(1); diff --git a/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.h b/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.h index 8e59798dc..cc147a736 100644 --- a/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.h +++ b/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.h @@ -132,7 +132,7 @@ private: [[nodiscard]] bool isFinished() const; private: - crl::time _startedAt = 0;; + crl::time _startedAt = 0; std::vector _animValues; PiePartData _current; bool _isFinished = true; diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index 393d303aa..512ef17ba 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -589,8 +589,9 @@ void writeAutoupdatePrefix(const QString &prefix) { QString readAutoupdatePrefix() { Expects(!Core::UpdaterDisabled()); + static const auto RegExp = QRegularExpression("/+$"); auto result = readAutoupdatePrefixRaw(); - return result.replace(QRegularExpression("/+$"), QString()); + return result.replace(RegExp, QString()); } void writeBackground(const Data::WallPaper &paper, const QImage &image) { @@ -623,7 +624,7 @@ void writeBackground(const Data::WallPaper &paper, const QImage &image) { dst = dst.subspan(sizeof(qint32)); bytes::copy(dst, bytes::object_as_span(&height)); dst = dst.subspan(sizeof(qint32)); - const auto src = bytes::make_span(image.constBits(), srcsize); + const auto src = bytes::make_span(copy.constBits(), srcsize); if (srcsize == dstsize) { bytes::copy(dst, src); } else { diff --git a/Telegram/SourceFiles/storage/storage_media_prepare.cpp b/Telegram/SourceFiles/storage/storage_media_prepare.cpp index 362455cac..17ebe996f 100644 --- a/Telegram/SourceFiles/storage/storage_media_prepare.cpp +++ b/Telegram/SourceFiles/storage/storage_media_prepare.cpp @@ -132,7 +132,6 @@ MimeDataState ComputeMimeDataState(const QMimeData *data) { return MimeDataState::None; } - auto files = QStringList(); auto allAreSmallImages = true; for (const auto &url : urls) { if (!url.isLocalFile()) { diff --git a/Telegram/SourceFiles/support/support_templates.cpp b/Telegram/SourceFiles/support/support_templates.cpp index ccfac3bf1..cb1515ee3 100644 --- a/Telegram/SourceFiles/support/support_templates.cpp +++ b/Telegram/SourceFiles/support/support_templates.cpp @@ -518,7 +518,6 @@ void Templates::ensureUpdatesCreated() { } void Templates::update() { - auto errors = QStringList(); const auto sendRequest = [&](const QString &path, const QString &url) { ensureUpdatesCreated(); if (_updates->requests.find(path) != end(_updates->requests)) { diff --git a/Telegram/SourceFiles/ui/boxes/country_select_box.cpp b/Telegram/SourceFiles/ui/boxes/country_select_box.cpp index 0d21dacdb..bb7cc70bf 100644 --- a/Telegram/SourceFiles/ui/boxes/country_select_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/country_select_box.cpp @@ -234,13 +234,14 @@ void CountrySelectBox::Inner::init() { } auto index = 0; for (const auto &info : _list) { + static const auto RegExp = QRegularExpression("[\\s\\-]"); auto full = info.country + ' ' + (!info.alternativeName.isEmpty() ? info.alternativeName : QString()); const auto namesList = std::move(full).toLower().split( - QRegularExpression("[\\s\\-]"), + RegExp, Qt::SkipEmptyParts); auto &names = _namesList.emplace_back(); names.reserve(namesList.size()); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 24a1a6e27..209444ff1 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -733,8 +733,9 @@ void Panel::switchInlineQueryMessage(const QJsonObject &args) { u"groups"_q, u"channels"_q, }; + const auto typeArray = args["chat_types"].toArray(); auto types = std::vector(); - for (const auto &value : args["chat_types"].toArray()) { + for (const auto &value : typeArray) { const auto type = value.toString(); if (valid.contains(type)) { types.push_back(type); @@ -810,8 +811,9 @@ void Panel::openPopup(const QJsonObject &args) { { "cancel", Type::Cancel }, { "destructive", Type::Destructive }, }; + const auto buttonArray = args["buttons"].toArray(); auto buttons = std::vector(); - for (const auto button : args["buttons"].toArray()) { + for (const auto button : buttonArray) { const auto fields = button.toObject(); const auto i = types.find(fields["type"].toString()); if (i == end(types)) { diff --git a/Telegram/SourceFiles/ui/chat/group_call_bar.cpp b/Telegram/SourceFiles/ui/chat/group_call_bar.cpp index 4b0ffb092..239028b1f 100644 --- a/Telegram/SourceFiles/ui/chat/group_call_bar.cpp +++ b/Telegram/SourceFiles/ui/chat/group_call_bar.cpp @@ -244,6 +244,7 @@ void GroupCallBar::setupInner() { } void GroupCallBar::setupRightButton(not_null button) { + button->setFullRadius(true); rpl::combine( _inner->widthValue(), button->widthValue() diff --git a/Telegram/SourceFiles/ui/controls/silent_toggle.cpp b/Telegram/SourceFiles/ui/controls/silent_toggle.cpp index 2968c80fd..9c5f88401 100644 --- a/Telegram/SourceFiles/ui/controls/silent_toggle.cpp +++ b/Telegram/SourceFiles/ui/controls/silent_toggle.cpp @@ -16,11 +16,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_chat_helpers.h" namespace Ui { -namespace { - -constexpr auto kAnimationDuration = crl::time(120); - -} // namespace SilentToggle::SilentToggle(QWidget *parent, not_null channel) : RippleButton(parent, st::historySilentToggle.ripple) diff --git a/Telegram/SourceFiles/ui/empty_userpic.cpp b/Telegram/SourceFiles/ui/empty_userpic.cpp index 7c46b7826..d2b11df2c 100644 --- a/Telegram/SourceFiles/ui/empty_userpic.cpp +++ b/Telegram/SourceFiles/ui/empty_userpic.cpp @@ -151,6 +151,38 @@ void PaintRepliesMessagesInner( fg); } +void PaintHiddenAuthorInner( + QPainter &p, + int x, + int y, + int size, + const style::color &fg) { + PaintIconInner( + p, + x, + y, + size, + st::defaultDialogRow.photoSize, + st::dialogsHiddenAuthorUserpic, + fg); +} + +void PaintMyNotesInner( + QPainter &p, + int x, + int y, + int size, + const style::color &fg) { + PaintIconInner( + p, + x, + y, + size, + st::defaultDialogRow.photoSize, + st::dialogsMyNotesUserpic, + fg); +} + void PaintExternalMessagesInner( QPainter &p, int x, @@ -397,6 +429,84 @@ QImage EmptyUserpic::GenerateRepliesMessages(int size) { }); } +void EmptyUserpic::PaintHiddenAuthor( + QPainter &p, + int x, + int y, + int outerWidth, + int size) { + auto bg = QLinearGradient(x, y, x, y + size); + bg.setStops({ + { 0., st::premiumButtonBg2->c }, + { 1., st::premiumButtonBg3->c }, + }); + const auto &fg = st::historyPeerUserpicFg; + PaintHiddenAuthor(p, x, y, outerWidth, size, QBrush(bg), fg); +} + +void EmptyUserpic::PaintHiddenAuthor( + QPainter &p, + int x, + int y, + int outerWidth, + int size, + QBrush bg, + const style::color &fg) { + x = style::RightToLeft() ? (outerWidth - x - size) : x; + + PainterHighQualityEnabler hq(p); + p.setBrush(bg); + p.setPen(Qt::NoPen); + p.drawEllipse(x, y, size, size); + + PaintHiddenAuthorInner(p, x, y, size, fg); +} + +QImage EmptyUserpic::GenerateHiddenAuthor(int size) { + return Generate(size, [&](QPainter &p) { + PaintHiddenAuthor(p, 0, 0, size, size); + }); +} + +void EmptyUserpic::PaintMyNotes( + QPainter &p, + int x, + int y, + int outerWidth, + int size) { + auto bg = QLinearGradient(x, y, x, y + size); + bg.setStops({ + { 0., st::historyPeerSavedMessagesBg->c }, + { 1., st::historyPeerSavedMessagesBg2->c } + }); + const auto &fg = st::historyPeerUserpicFg; + PaintMyNotes(p, x, y, outerWidth, size, QBrush(bg), fg); +} + +void EmptyUserpic::PaintMyNotes( + QPainter &p, + int x, + int y, + int outerWidth, + int size, + QBrush bg, + const style::color &fg) { + x = style::RightToLeft() ? (outerWidth - x - size) : x; + + PainterHighQualityEnabler hq(p); + p.setBrush(bg); + p.setPen(Qt::NoPen); + p.drawEllipse(x, y, size, size); + + PaintMyNotesInner(p, x, y, size, fg); +} + +QImage EmptyUserpic::GenerateMyNotes(int size) { + return Generate(size, [&](QPainter &p) { + PaintMyNotes(p, 0, 0, size, size); + }); +} + std::pair EmptyUserpic::uniqueKey() const { const auto first = (uint64(0xFFFFFFFFU) << 32) | anim::getPremultiplied(_colors.color1->c); diff --git a/Telegram/SourceFiles/ui/empty_userpic.h b/Telegram/SourceFiles/ui/empty_userpic.h index f71335a6a..7d293db54 100644 --- a/Telegram/SourceFiles/ui/empty_userpic.h +++ b/Telegram/SourceFiles/ui/empty_userpic.h @@ -81,6 +81,38 @@ public: const style::color &fg); [[nodiscard]] static QImage GenerateRepliesMessages(int size); + static void PaintHiddenAuthor( + QPainter &p, + int x, + int y, + int outerWidth, + int size); + static void PaintHiddenAuthor( + QPainter &p, + int x, + int y, + int outerWidth, + int size, + QBrush bg, + const style::color &fg); + [[nodiscard]] static QImage GenerateHiddenAuthor(int size); + + static void PaintMyNotes( + QPainter &p, + int x, + int y, + int outerWidth, + int size); + static void PaintMyNotes( + QPainter &p, + int x, + int y, + int outerWidth, + int size, + QBrush bg, + const style::color &fg); + [[nodiscard]] static QImage GenerateMyNotes(int size); + ~EmptyUserpic(); private: diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index 5c689eb2e..96497be04 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -910,7 +910,6 @@ TextWithEntities Manager::ComposeReactionEmoji( return TextWithEntities{ *emoji }; } const auto id = v::get(reaction.data); - auto entities = EntitiesInText(); const auto document = session->data().document(id); const auto sticker = document->sticker(); const auto text = sticker ? sticker->alt : PlaceholderReactionText(); diff --git a/Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp b/Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp index ac217b30b..bd1e5c311 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp @@ -819,7 +819,7 @@ EditorBlock::Row &EditorBlock::rowAtIndex(int index) { } int EditorBlock::findRowIndex(const QString &name) const { - return _indices.value(name, -1);; + return _indices.value(name, -1); } EditorBlock::Row *EditorBlock::findRow(const QString &name) { diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 5556864aa..e5764b193 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -76,6 +76,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_forum.h" #include "data/data_forum_topic.h" #include "data/data_user.h" +#include "data/data_saved_sublist.h" #include "data/data_scheduled_messages.h" #include "data/data_histories.h" #include "data/data_chat_filters.h" @@ -244,6 +245,7 @@ private: void fillRepliesActions(); void fillScheduledActions(); void fillArchiveActions(); + void fillSavedSublistActions(); void fillContextMenuActions(); void addHidePromotion(); @@ -293,6 +295,7 @@ private: Data::ForumTopic *_topic = nullptr; PeerData *_peer = nullptr; Data::Folder *_folder = nullptr; + Data::SavedSublist *_sublist = nullptr; const PeerMenuCallback &_addAction; }; @@ -319,17 +322,21 @@ void AddChatMembers( bool PinnedLimitReached( not_null controller, - not_null thread) { - const auto owner = &thread->owner(); - if (owner->pinnedCanPin(thread)) { + not_null entry) { + const auto owner = &entry->owner(); + if (owner->pinnedCanPin(entry)) { return false; } // Some old chat, that was converted, maybe is still pinned. - const auto history = thread->asHistory(); - if (!history) { - controller->show(Box(ForumPinsLimitBox, thread->asTopic()->forum())); + if (const auto sublist = entry->asSublist()) { + controller->show(Box(SublistsPinsLimitBox, &sublist->session())); + return true; + } else if (const auto topic = entry->asTopic()) { + controller->show(Box(ForumPinsLimitBox, topic->forum())); return true; } + const auto history = entry->asHistory(); + Assert(history != nullptr); const auto folder = history->folder(); const auto wasted = FindWastedPin(owner, folder); if (wasted) { @@ -359,18 +366,18 @@ bool PinnedLimitReached( void TogglePinnedThread( not_null controller, - not_null thread) { - if (!thread->folderKnown()) { + not_null entry) { + if (!entry->folderKnown()) { return; } - const auto owner = &thread->owner(); - const auto isPinned = !thread->isPinnedDialog(FilterId()); - if (isPinned && PinnedLimitReached(controller, thread)) { + const auto owner = &entry->owner(); + const auto isPinned = !entry->isPinnedDialog(FilterId()); + if (isPinned && PinnedLimitReached(controller, entry)) { return; } - owner->setChatPinned(thread, FilterId(), isPinned); - if (const auto history = thread->asHistory()) { + owner->setChatPinned(entry, FilterId(), isPinned); + if (const auto history = entry->asHistory()) { const auto flags = isPinned ? MTPmessages_ToggleDialogPin::Flag::f_pinned : MTPmessages_ToggleDialogPin::Flag(0); @@ -383,7 +390,7 @@ void TogglePinnedThread( if (isPinned) { controller->content()->dialogsToUp(); } - } else if (const auto topic = thread->asTopic()) { + } else if (const auto topic = entry->asTopic()) { owner->session().api().request(MTPchannels_UpdatePinnedForumTopic( topic->channel()->inputChannel, MTP_int(topic->rootId()), @@ -391,17 +398,30 @@ void TogglePinnedThread( )).done([=](const MTPUpdates &result) { owner->session().api().applyUpdates(result); }).send(); + } else if (const auto sublist = entry->asSublist()) { + const auto flags = isPinned + ? MTPmessages_ToggleSavedDialogPin::Flag::f_pinned + : MTPmessages_ToggleSavedDialogPin::Flag(0); + owner->session().api().request(MTPmessages_ToggleSavedDialogPin( + MTP_flags(flags), + MTP_inputDialogPeer(sublist->peer()->input) + )).done([=] { + owner->notifyPinnedDialogsOrderUpdated(); + }).send(); + //if (isPinned) { + // controller->content()->dialogsToUp(); + //} } } void TogglePinnedThread( not_null controller, - not_null thread, + not_null entry, FilterId filterId) { if (!filterId) { - return TogglePinnedThread(controller, thread); + return TogglePinnedThread(controller, entry); } - const auto history = thread->asHistory(); + const auto history = entry->asHistory(); if (!history) { return; } @@ -438,6 +458,7 @@ Filler::Filler( , _topic(request.key.topic()) , _peer(request.key.peer()) , _folder(request.key.folder()) +, _sublist(request.key.sublist()) , _addAction(addAction) { } @@ -471,21 +492,21 @@ void Filler::addToggleTopicClosed() { } void Filler::addTogglePin() { - if (!_peer || (_topic && !_topic->canTogglePinned())) { + if ((!_sublist && !_peer) || (_topic && !_topic->canTogglePinned())) { return; } const auto controller = _controller; const auto filterId = _request.filterId; - const auto thread = _request.key.thread(); - if (!thread || thread->fixedOnTopIndex()) { + const auto entry = _thread ? (Dialogs::Entry*)_thread : _sublist; + if (!entry || entry->fixedOnTopIndex()) { return; } const auto pinText = [=] { - return thread->isPinnedDialog(filterId) + return entry->isPinnedDialog(filterId) ? tr::lng_context_unpin_from_top(tr::now) : tr::lng_context_pin_to_top(tr::now); }; - const auto weak = base::make_weak(thread); + const auto weak = base::make_weak(entry); const auto pinToggle = [=] { if (const auto strong = weak.get()) { TogglePinnedThread(controller, strong, filterId); @@ -494,13 +515,13 @@ void Filler::addTogglePin() { _addAction( pinText(), pinToggle, - (thread->isPinnedDialog(filterId) + (entry->isPinnedDialog(filterId) ? &st::menuIconUnpin : &st::menuIconPin)); } void Filler::addToggleMuteSubmenu(bool addSeparator) { - if (_thread->peer()->isSelf()) { + if (!_thread || _thread->peer()->isSelf()) { return; } PeerMenuAddMuteSubmenuAction(_controller, _thread, _addAction); @@ -526,6 +547,8 @@ void Filler::addSupportInfo() { void Filler::addInfo() { if (_peer && (_peer->isSelf() || _peer->isRepliesChat())) { return; + } else if (!_thread) { + return; } else if (_controller->adaptive().isThreeColumn()) { const auto thread = _controller->activeChatCurrent().thread(); if (thread && thread == _thread) { @@ -534,8 +557,6 @@ void Filler::addInfo() { return; } } - } else if (!_thread) { - return; } const auto controller = _controller; const auto weak = base::make_weak(_thread); @@ -1103,7 +1124,8 @@ void Filler::addGiftPremium() { || user->isBot() || user->isNotificationsUser() || !user->canReceiveGifts() - || user->isRepliesChat()) { + || user->isRepliesChat() + || !user->session().premiumCanBuy()) { return; } @@ -1116,9 +1138,9 @@ void Filler::addGiftPremium() { void Filler::fill() { if (_folder) { fillArchiveActions(); - return; - } - switch (_request.section) { + } else if (_sublist) { + fillSavedSublistActions(); + } else switch (_request.section) { case Section::ChatsList: fillChatsListActions(); break; case Section::History: fillHistoryActions(); break; case Section::Profile: fillProfileActions(); break; @@ -1353,6 +1375,10 @@ void Filler::fillArchiveActions() { }, &st::menuIconManage); } +void Filler::fillSavedSublistActions() { + addTogglePin(); +} + } // namespace void PeerMenuExportChat(not_null peer) { diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 5af2c4148..fb334476b 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1222,8 +1222,8 @@ void SessionController::showGiftPremiumBox(UserData *user) { } } -void SessionController::showGiftPremiumsBox() { - _giftPremiumValidator.showChoosePeerBox(); +void SessionController::showGiftPremiumsBox(const QString &ref) { + _giftPremiumValidator.showChoosePeerBox(ref); } void SessionController::init() { diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 885eb812e..ac23dfae4 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -380,7 +380,7 @@ public: void showEditPeerBox(PeerData *peer); void showGiftPremiumBox(UserData *user); - void showGiftPremiumsBox(); + void showGiftPremiumsBox(const QString &ref); void enableGifPauseReason(GifPauseReason reason); void disableGifPauseReason(GifPauseReason reason); diff --git a/Telegram/ThirdParty/hunspell b/Telegram/ThirdParty/hunspell index 2969be996..22c3381e2 160000 --- a/Telegram/ThirdParty/hunspell +++ b/Telegram/ThirdParty/hunspell @@ -1 +1 @@ -Subproject commit 2969be996acad84b91ab3875b1816636fe61a40e +Subproject commit 22c3381e2066bed616250d373fc5c935598b564a diff --git a/Telegram/ThirdParty/minizip/ioapi.c b/Telegram/ThirdParty/minizip/ioapi.c index 814a6fd38..0ca29db6a 100644 --- a/Telegram/ThirdParty/minizip/ioapi.c +++ b/Telegram/ThirdParty/minizip/ioapi.c @@ -231,8 +231,7 @@ static int ZCALLBACK ferror_file_func (voidpf opaque, voidpf stream) return ret; } -void fill_fopen_filefunc (pzlib_filefunc_def) - zlib_filefunc_def* pzlib_filefunc_def; +void fill_fopen_filefunc (zlib_filefunc_def* pzlib_filefunc_def) { pzlib_filefunc_def->zopen_file = fopen_file_func; pzlib_filefunc_def->zread_file = fread_file_func; diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 20d988f2c..253b0207a 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -24,14 +24,14 @@ RUN dnf -y install epel-release \ && dnf -y install autoconf automake libtool pkgconfig make patch git \ python3.11-pip python3.11-devel gperf flex bison clang lld yasm \ file which perl-open perl-XML-Parser xorg-x11-util-macros \ - gcc-toolset-13-gcc gcc-toolset-13-gcc-c++ gcc-toolset-13-binutils \ + gcc-toolset-12-gcc gcc-toolset-12-gcc-c++ gcc-toolset-12-binutils \ libffi-devel fontconfig-devel freetype-devel libX11-devel \ alsa-lib-devel pulseaudio-libs-devel mesa-libGL-devel mesa-libEGL-devel \ mesa-libgbm-devel libdrm-devel vulkan-devel libva-devel libvdpau-devel \ glib2-devel at-spi2-core-devel gtk3-devel boost1.78-devel fmt-devel \ && dnf clean all -SHELL [ "bash", "-c", ". /opt/rh/gcc-toolset-13/enable; exec bash -c \"$@\"", "-s"] +SHELL [ "bash", "-c", ". /opt/rh/gcc-toolset-12/enable; exec bash -c \"$@\"", "-s"] WORKDIR {{ LibrariesPath }} @@ -54,7 +54,7 @@ FROM builder AS patches RUN git init patches \ && cd patches \ && git remote add origin {{ GIT }}/desktop-app/patches.git \ - && git fetch --depth=1 origin 12780ae73abe21d00ae125e284d8bca9d8993948 \ + && git fetch --depth=1 origin 78fe199b743df0358c04502d6947cd42bb5d16ed \ && git reset --hard FETCH_HEAD \ && rm -rf .git @@ -906,5 +906,5 @@ ENV BOOST_INCLUDEDIR /usr/include/boost1.78 ENV BOOST_LIBRARYDIR /usr/lib64/boost1.78 VOLUME [ "/usr/src/tdesktop" ] -ENTRYPOINT [ "scl", "enable", "gcc-toolset-13", "--" ] +ENTRYPOINT [ "scl", "enable", "gcc-toolset-12", "--" ] CMD [ "/usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh" ] diff --git a/Telegram/build/version b/Telegram/build/version index aeb48c63a..fa61513ba 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 4013001 -AppVersionStrMajor 4.13 -AppVersionStrSmall 4.13.1 -AppVersionStr 4.13.1 +AppVersion 4014001 +AppVersionStrMajor 4.14 +AppVersionStrSmall 4.14.1 +AppVersionStr 4.14.1 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 4.13.1 +AppVersionOriginal 4.14.1 diff --git a/Telegram/codegen b/Telegram/codegen index 805b851d6..afed06a4c 160000 --- a/Telegram/codegen +++ b/Telegram/codegen @@ -1 +1 @@ -Subproject commit 805b851d69444ab3659b619566d186d0be83dcd9 +Subproject commit afed06a4c04d1a1cf7cfce4faca273e1f574462e diff --git a/Telegram/lib_base b/Telegram/lib_base index 73b0e8fcc..0d111bd46 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 73b0e8fcca59d754b60c22d5cd28c3995bf902ea +Subproject commit 0d111bd4633324533195aa0a840730b4bf6ba75b diff --git a/Telegram/lib_spellcheck b/Telegram/lib_spellcheck index 10cd8056b..96543c171 160000 --- a/Telegram/lib_spellcheck +++ b/Telegram/lib_spellcheck @@ -1 +1 @@ -Subproject commit 10cd8056be61aa2ba79e7580f8f459285ab63559 +Subproject commit 96543c1716d3790ef12bdec6b113958427710441 diff --git a/Telegram/lib_webview b/Telegram/lib_webview index c83478cd5..1bb91474c 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit c83478cd5ce2eb6fb343d9c96cec31688fe6d5f6 +Subproject commit 1bb91474c2337396673dd8a7c68e65ea317b6db5 diff --git a/changelog.txt b/changelog.txt index 2ab50bd14..d3ae5b8fa 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,15 @@ +4.14.1 (01.01.24) + +- Fix crash in "Author Hidden" chat in "Saved Messages". +- Improve jump-to-original button layout in "Saved Messages". +- Show my own chat as "My Notes" in "Saved Messages". +- In screen sharing source window select first screen by default. + +4.14 (31.12.23) + +- Improved saved messages. +- One-time voice messages. + 4.13.1 (23.12.23) - Fix crash in chat history right click. diff --git a/cmake b/cmake index 92f27add1..4005d7bef 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 92f27add11ae4280939079249d0f9da933ece6ad +Subproject commit 4005d7befb3ffbbbb7851ed767d5b58373958e6d