From bb3f8fbbe86528b181d36a0e176bd98bff6b80da Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 5 Feb 2024 16:20:46 +0400 Subject: [PATCH 01/73] Check whether webview is destructed after Webview::Window::init --- Telegram/SourceFiles/payments/ui/payments_panel.cpp | 4 ++++ Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.cpp b/Telegram/SourceFiles/payments/ui/payments_panel.cpp index bc2817d52..a672f81fc 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_panel.cpp @@ -619,6 +619,10 @@ postEvent: function(eventType, eventData) { } };)"); + if (!_webview) { + return false; + } + setupProgressGeometry(); return true; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index c34db9164..61ed29c59 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -703,6 +703,10 @@ postEvent: function(eventType, eventData) { } };)"); + if (!_webview) { + return false; + } + setupProgressGeometry(); return true; From 5334096d6828eb8c22797703d46c66a2839edca5 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 6 Feb 2024 10:17:12 +0400 Subject: [PATCH 02/73] Fix bot webview height with fractional scaling --- Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 61ed29c59..ab84a81cd 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -579,7 +579,7 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) { height = _mainButton->height(); } bottom->move(inner.x(), inner.y() + inner.height() - height); - container->resize(inner.width(), inner.height() - height); + container->setFixedSize(inner.width(), inner.height() - height); bottom->resizeToWidth(inner.width()); }, bottom->lifetime()); container->show(); @@ -1181,7 +1181,7 @@ void Panel::createMainButton() { } button->move(inner.x(), inner.y() + inner.height() - height); if (const auto raw = _webviewParent.data()) { - raw->resize(inner.width(), inner.height() - height); + raw->setFixedSize(inner.width(), inner.height() - height); } button->resizeToWidth(inner.width()); _webviewBottom->setVisible(!shown); From 29debc07c4ea0bb2691f0a8c1fe5ab558592edcf Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 6 Feb 2024 19:12:38 +0400 Subject: [PATCH 03/73] Let specify arbitrary build configuration via Docker Default to RelWithDebInfo for both optimizations and debug information --- .github/workflows/linux.yml | 2 +- Telegram/build/docker/centos_env/build.sh | 7 +------ docs/building-linux.md | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 6ac646b3c..fe6d3752c 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -85,7 +85,7 @@ jobs: docker run --rm \ -v $PWD:/usr/src/tdesktop \ - -e DEBUG=1 \ + -e CONFIG=Debug \ tdesktop:centos_env \ /usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \ -D CMAKE_C_FLAGS_DEBUG="" \ diff --git a/Telegram/build/docker/centos_env/build.sh b/Telegram/build/docker/centos_env/build.sh index 6aee294a4..0bf1aae04 100755 --- a/Telegram/build/docker/centos_env/build.sh +++ b/Telegram/build/docker/centos_env/build.sh @@ -3,9 +3,4 @@ set -e cd Telegram ./configure.sh "$@" - -if [ -n "$DEBUG" ]; then - cmake --build ../out --config Debug --parallel -else - cmake --build ../out --config Release --parallel -fi +cmake --build ../out --config "${CONFIG:-RelWithDebInfo}" --parallel diff --git a/docs/building-linux.md b/docs/building-linux.md index 6be5a32b4..4fa4503b5 100644 --- a/docs/building-linux.md +++ b/docs/building-linux.md @@ -30,7 +30,7 @@ Or, to create a debug build, run (also using [your **api_id** and **api_hash**]( docker run --rm -it \ -v $PWD:/usr/src/tdesktop \ - -e DEBUG=1 \ + -e CONFIG=Debug \ tdesktop:centos_env \ /usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \ -D TDESKTOP_API_ID=YOUR_API_ID \ From fd3ce905c0c04dfd4ccce8575b105b9423549803 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 8 Feb 2024 11:42:05 +0400 Subject: [PATCH 04/73] Update lib_webview --- Telegram/lib_webview | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_webview b/Telegram/lib_webview index 75cc4c04d..db36c9935 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit 75cc4c04d28a09172b1df596913c09161aaede35 +Subproject commit db36c99359b0c1cb376c8fa6f5f7ab80807eb45f From b9592621406245154630040987f7dc4afa50bacf Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 8 Feb 2024 15:20:33 +0300 Subject: [PATCH 05/73] Fixed legal link in some files. --- Telegram/SourceFiles/intro/intro_code_input.cpp | 13 +++++++------ Telegram/SourceFiles/intro/intro_code_input.h | 13 +++++++------ Telegram/SourceFiles/lang/lang_pch.h | 13 +++++++------ .../SourceFiles/ui/controls/call_mute_button.cpp | 13 +++++++------ Telegram/SourceFiles/ui/controls/call_mute_button.h | 13 +++++++------ Telegram/SourceFiles/ui/ui_pch.h | 13 +++++++------ 6 files changed, 42 insertions(+), 36 deletions(-) diff --git a/Telegram/SourceFiles/intro/intro_code_input.cpp b/Telegram/SourceFiles/intro/intro_code_input.cpp index 5c0005b20..968cd87d4 100644 --- a/Telegram/SourceFiles/intro/intro_code_input.cpp +++ b/Telegram/SourceFiles/intro/intro_code_input.cpp @@ -1,9 +1,10 @@ -// This file is part of Desktop App Toolkit, -// a set of libraries for developing nice desktop applications. -// -// For license and copyright information please follow this link: -// https://github.com/desktop-app/legal/blob/master/LEGAL -// +/* +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 "intro/intro_code_input.h" #include "lang/lang_keys.h" diff --git a/Telegram/SourceFiles/intro/intro_code_input.h b/Telegram/SourceFiles/intro/intro_code_input.h index f929142df..9b5094aa8 100644 --- a/Telegram/SourceFiles/intro/intro_code_input.h +++ b/Telegram/SourceFiles/intro/intro_code_input.h @@ -1,9 +1,10 @@ -// This file is part of Desktop App Toolkit, -// a set of libraries for developing nice desktop applications. -// -// For license and copyright information please follow this link: -// https://github.com/desktop-app/legal/blob/master/LEGAL -// +/* +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 "ui/rp_widget.h" diff --git a/Telegram/SourceFiles/lang/lang_pch.h b/Telegram/SourceFiles/lang/lang_pch.h index 53a811432..175adb938 100644 --- a/Telegram/SourceFiles/lang/lang_pch.h +++ b/Telegram/SourceFiles/lang/lang_pch.h @@ -1,9 +1,10 @@ -// This file is part of Desktop App Toolkit, -// a set of libraries for developing nice desktop applications. -// -// For license and copyright information please follow this link: -// https://github.com/desktop-app/legal/blob/master/LEGAL -// +/* +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 #include diff --git a/Telegram/SourceFiles/ui/controls/call_mute_button.cpp b/Telegram/SourceFiles/ui/controls/call_mute_button.cpp index b913f3428..dfb31807e 100644 --- a/Telegram/SourceFiles/ui/controls/call_mute_button.cpp +++ b/Telegram/SourceFiles/ui/controls/call_mute_button.cpp @@ -1,9 +1,10 @@ -// This file is part of Desktop App Toolkit, -// a set of libraries for developing nice desktop applications. -// -// For license and copyright information please follow this link: -// https://github.com/desktop-app/legal/blob/master/LEGAL -// +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ #include "ui/controls/call_mute_button.h" #include "base/flat_map.h" diff --git a/Telegram/SourceFiles/ui/controls/call_mute_button.h b/Telegram/SourceFiles/ui/controls/call_mute_button.h index f3d072638..b6755f543 100644 --- a/Telegram/SourceFiles/ui/controls/call_mute_button.h +++ b/Telegram/SourceFiles/ui/controls/call_mute_button.h @@ -1,9 +1,10 @@ -// This file is part of Desktop App Toolkit, -// a set of libraries for developing nice desktop applications. -// -// For license and copyright information please follow this link: -// https://github.com/desktop-app/legal/blob/master/LEGAL -// +/* +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 "base/unique_qptr.h" diff --git a/Telegram/SourceFiles/ui/ui_pch.h b/Telegram/SourceFiles/ui/ui_pch.h index 81a3e0adc..84a912f8f 100644 --- a/Telegram/SourceFiles/ui/ui_pch.h +++ b/Telegram/SourceFiles/ui/ui_pch.h @@ -1,9 +1,10 @@ -// This file is part of Desktop App Toolkit, -// a set of libraries for developing nice desktop applications. -// -// For license and copyright information please follow this link: -// https://github.com/desktop-app/legal/blob/master/LEGAL -// +/* +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 #include From 0e571ea679c3223438e70a2fa35c2f3f7f7f50a5 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 8 Feb 2024 17:32:25 +0300 Subject: [PATCH 06/73] Added ability to copy filename of named documents. --- Telegram/Resources/langs/lang.strings | 1 + .../admin_log/history_admin_log_inner.cpp | 7 ++++ .../history/history_inner_widget.cpp | 5 +++ .../view/history_view_context_menu.cpp | 32 +++++++++++++++++++ .../history/view/history_view_context_menu.h | 4 +++ 5 files changed, 49 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 04dab8e1e..601366ee4 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2752,6 +2752,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_copy_email" = "Copy Email Address"; "lng_context_copy_hashtag" = "Copy Hashtag"; "lng_context_copy_mention" = "Copy Username"; +"lng_context_copy_filename" = "Copy Filename"; "lng_context_save_image" = "Save Image As..."; "lng_context_copy_image" = "Copy Image"; "lng_context_cancel_download" = "Cancel Download"; 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 d652d0f4c..b7bb4b9e0 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_text.h" #include "history/admin_log/history_admin_log_section.h" #include "history/admin_log/history_admin_log_filter.h" +#include "history/view/history_view_context_menu.h" #include "history/view/history_view_message.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_cursor_state.h" @@ -1251,6 +1252,12 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _menu->addAction(lnkIsVideo ? tr::lng_context_save_video(tr::now) : (lnkIsVoice ? tr::lng_context_save_audio(tr::now) : (lnkIsAudio ? tr::lng_context_save_audio_file(tr::now) : tr::lng_context_save_file(tr::now))), base::fn_delayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, lnkDocument] { saveDocumentToFile(lnkDocument); }), &st::menuIconDownload); + + HistoryView::AddCopyFilename( + _menu, + lnkDocument, + [] { return false; }); + if (lnkDocument->hasAttachedStickers()) { const auto controller = _controller; auto callback = [=] { diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 8d008113b..b7955f4ba 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2223,6 +2223,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _menu->addAction(lnkIsVideo ? tr::lng_context_save_video(tr::now) : (lnkIsVoice ? tr::lng_context_save_audio(tr::now) : (lnkIsAudio ? tr::lng_context_save_audio_file(tr::now) : tr::lng_context_save_file(tr::now))), base::fn_delayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [=] { saveDocumentToFile(itemId, document); }), &st::menuIconDownload); + + HistoryView::AddCopyFilename( + _menu, + document, + [=] { return showCopyRestrictionForSelected(); }); } if (document->hasAttachedStickers()) { _menu->addAction(tr::lng_context_attached_stickers(tr::now), [=] { diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 9183ab81b..9267aeb5c 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/menu/menu_multiline_action.h" #include "ui/image/image.h" #include "ui/toast/toast.h" +#include "ui/text/format_song_document_name.h" #include "ui/text/text_utilities.h" #include "ui/controls/delete_message_context_action.h" #include "ui/controls/who_reacted_context_action.h" @@ -326,6 +327,10 @@ void AddDocumentActions( AddSaveSoundForNotifications(menu, item, document, controller); } AddSaveDocumentAction(menu, item, document, list); + AddCopyFilename( + menu, + document, + [=] { return list->showCopyRestrictionForSelected(); }); } void AddPostLinkAction( @@ -1555,6 +1560,33 @@ void ShowTagInListMenu( (*menu)->popup(position); } +void AddCopyFilename( + not_null menu, + not_null document, + Fn showCopyRestrictionForSelected) { + const auto filenameToCopy = [&] { + if (document->isAudioFile()) { + return TextForMimeData().append( + Ui::Text::FormatSongNameFor(document).string()); + } else if (document->sticker() + || document->isAnimation() + || document->isVideoMessage() + || document->isVideoFile() + || document->isVoiceMessage()) { + return TextForMimeData(); + } else { + return TextForMimeData().append(document->filename()); + } + }(); + if (!filenameToCopy.empty()) { + menu->addAction(tr::lng_context_copy_filename(tr::now), [=] { + if (!showCopyRestrictionForSelected()) { + TextUtilities::SetClipboardText(filenameToCopy); + } + }, &st::menuIconCopy); + } +} + void ShowWhoReactedMenu( not_null*> menu, QPoint position, diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.h b/Telegram/SourceFiles/history/view/history_view_context_menu.h index 65afd007b..8f00f4da8 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.h +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.h @@ -94,6 +94,10 @@ void ShowTagInListMenu( not_null context, const Data::ReactionId &id, not_null controller); +void AddCopyFilename( + not_null menu, + not_null document, + Fn showCopyRestrictionForSelected); enum class EmojiPacksSource { Message, From 5d3400033afe7c41c0e1ff28057917d1a667ce21 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 8 Feb 2024 17:35:29 +0300 Subject: [PATCH 07/73] Fixed ability to copy whole transcribed text and copy album captions. --- .../SourceFiles/data/data_media_types.cpp | 69 ++++++------------- Telegram/SourceFiles/data/data_media_types.h | 4 -- .../SourceFiles/history/history_item_text.cpp | 24 ++++--- 3 files changed, 37 insertions(+), 60 deletions(-) diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index eee8ac7e1..7361373df 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -302,20 +302,27 @@ bool UpdateExtendedMedia( }); } -} // namespace - TextForMimeData WithCaptionClipboardText( const QString &attachType, TextForMimeData &&caption) { auto result = TextForMimeData(); - result.reserve(5 + attachType.size() + caption.expanded.size()); - result.append(u"[ "_q).append(attachType).append(u" ]"_q); - if (!caption.empty()) { - result.append('\n').append(std::move(caption)); + if (attachType.isEmpty()) { + result.reserve(1 + caption.expanded.size()); + if (!caption.empty()) { + result.append(std::move(caption)); + } + } else { + result.reserve(5 + attachType.size() + caption.expanded.size()); + result.append(u"[ "_q).append(attachType).append(u" ]"_q); + if (!caption.empty()) { + result.append('\n').append(std::move(caption)); + } } return result; } +} // namespace + Invoice ComputeInvoiceData( not_null item, const MTPDmessageMediaInvoice &data) { @@ -767,9 +774,7 @@ QString MediaPhoto::pinnedTextSubstring() const { } TextForMimeData MediaPhoto::clipboardText() const { - return WithCaptionClipboardText( - tr::lng_in_dlg_photo(tr::now), - parent()->clipboardText()); + return TextForMimeData(); } bool MediaPhoto::allowsEditCaption() const { @@ -1072,42 +1077,9 @@ QString MediaFile::pinnedTextSubstring() const { } TextForMimeData MediaFile::clipboardText() const { - const auto attachType = [&] { - const auto name = Ui::Text::FormatSongNameFor(_document).string(); - const auto addName = !name.isEmpty() - ? u" : "_q + name - : QString(); - if (const auto sticker = _document->sticker()) { - if (!_emoji.isEmpty()) { - return tr::lng_in_dlg_sticker_emoji( - tr::now, - lt_emoji, - _emoji); - } - return tr::lng_in_dlg_sticker(tr::now); - } else if (_document->isAnimation()) { - if (_document->isVideoMessage()) { - const auto media = parent()->media(); - return (media && media->ttlSeconds()) - ? tr::lng_in_dlg_video_message_ttl(tr::now) - : tr::lng_in_dlg_video_message(tr::now); - } - return u"GIF"_q; - } else if (_document->isVideoFile()) { - return tr::lng_in_dlg_video(tr::now); - } else if (_document->isVoiceMessage()) { - const auto media = parent()->media(); - return ((media && media->ttlSeconds()) - ? tr::lng_in_dlg_voice_message_ttl - : tr::lng_in_dlg_audio)(tr::now) + addName;; - } else if (_document->isSong()) { - return tr::lng_in_dlg_audio_file(tr::now) + addName; - } - return tr::lng_in_dlg_file(tr::now) + addName; - }(); auto caption = parent()->clipboardText(); - if (_document->isVoiceMessage()) { + if (_document->isVoiceMessage() || _document->isVideoMessage()) { const auto &entry = _document->session().api().transcribes().entry( parent()); if (!entry.requestId @@ -1115,17 +1087,18 @@ TextForMimeData MediaFile::clipboardText() const { && !entry.toolong && !entry.failed && (entry.pending || !entry.result.isEmpty())) { - const auto text = "{{\n" + const auto hasCaption = !caption.rich.text.isEmpty(); + const auto text = (hasCaption ? "{{\n" : "") + entry.result + (entry.result.isEmpty() ? "" : " ") + (entry.pending ? "[...]" : "") - + "\n}}" - + (caption.rich.text.isEmpty() ? "" : "\n"); - caption = TextForMimeData{ text, { text } }.append(std::move(caption)); + + (hasCaption ? "\n}}\n" : ""); + caption = TextForMimeData{ text, { text } }.append( + std::move(caption)); } } - return WithCaptionClipboardText(attachType, std::move(caption)); + return caption; } bool MediaFile::allowsEditCaption() const { diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index b2323af8a..1aae67928 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -709,10 +709,6 @@ private: }; -[[nodiscard]] TextForMimeData WithCaptionClipboardText( - const QString &attachType, - TextForMimeData &&caption); - [[nodiscard]] Invoice ComputeInvoiceData( not_null item, const MTPDmessageMediaInvoice &data); diff --git a/Telegram/SourceFiles/history/history_item_text.cpp b/Telegram/SourceFiles/history/history_item_text.cpp index 9bcbee704..1c66c59a4 100644 --- a/Telegram/SourceFiles/history/history_item_text.cpp +++ b/Telegram/SourceFiles/history/history_item_text.cpp @@ -17,15 +17,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_options.h" TextForMimeData HistoryItemText(not_null item) { - auto textResult = item->clipboardText(); + const auto media = item->media(); + + auto mediaResult = media ? media->clipboardText() : TextForMimeData(); + auto textResult = mediaResult.empty() + ? item->clipboardText() + : TextForMimeData(); auto logEntryOriginalResult = [&] { const auto entry = item->Get(); if (!entry) { return TextForMimeData(); } - const auto title = TextUtilities::SingleLine(entry->page->title.isEmpty() - ? entry->page->author - : entry->page->title); + const auto title = TextUtilities::SingleLine( + entry->page->title.isEmpty() + ? entry->page->author + : entry->page->title); auto titleResult = TextForMimeData::Rich( TextUtilities::ParseEntities( title, @@ -41,6 +47,11 @@ TextForMimeData HistoryItemText(not_null item) { return titleResult; }(); auto result = textResult; + if (result.empty()) { + result = std::move(mediaResult); + } else if (!mediaResult.empty()) { + result.append(qstr("\n\n")).append(std::move(mediaResult)); + } if (result.empty()) { result = std::move(logEntryOriginalResult); } else if (!logEntryOriginalResult.empty()) { @@ -78,7 +89,7 @@ TextForMimeData HistoryGroupText(not_null group) { return result; } } - auto caption = [&] { + return [&] { auto &&nonempty = ranges::views::all( group->items ) | ranges::views::filter( @@ -92,7 +103,4 @@ TextForMimeData HistoryGroupText(not_null group) { auto result = (*first)->clipboardText(); return (++first == end) ? result : TextForMimeData(); }(); - return Data::WithCaptionClipboardText( - tr::lng_in_dlg_album(tr::now), - std::move(caption)); } From bacab01f7e8367a3438e62e0df8127abd235bf6b Mon Sep 17 00:00:00 2001 From: 100backslash001 Date: Mon, 5 Feb 2024 17:45:23 +0400 Subject: [PATCH 08/73] Remove duplicates from configure.py --- Telegram/configure.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Telegram/configure.py b/Telegram/configure.py index fe24b9273..ceb27bfb9 100644 --- a/Telegram/configure.py +++ b/Telegram/configure.py @@ -25,8 +25,6 @@ def error(message): if sys.platform == 'win32' and 'COMSPEC' not in os.environ: error('COMSPEC environment variable is not set.') -executePath = os.getcwd() -scriptPath = os.path.dirname(os.path.realpath(__file__)) scriptName = os.path.basename(scriptPath) arguments = sys.argv[1:] From 8a62bacaa6be674ed0372504aed45fd8800f8434 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 8 Feb 2024 18:51:43 +0400 Subject: [PATCH 09/73] Fix anti-aliasing in emoji categories search. --- Telegram/SourceFiles/ui/controls/tabbed_search.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/ui/controls/tabbed_search.cpp b/Telegram/SourceFiles/ui/controls/tabbed_search.cpp index 6e5165aa8..6aaea70dc 100644 --- a/Telegram/SourceFiles/ui/controls/tabbed_search.cpp +++ b/Telegram/SourceFiles/ui/controls/tabbed_search.cpp @@ -181,6 +181,7 @@ void GroupsStrip::paintEvent(QPaintEvent *e) { const auto top = 0; const auto size = SearchWithGroups::IconSizeOverride(); if (_chosen == index) { + auto hq = PainterHighQualityEnabler(p); p.setPen(Qt::NoPen); p.setBrush(_st.bgActive); p.drawEllipse( From a10d668131033194926a1571d119bd9f5898c646 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 9 Feb 2024 20:18:40 +0400 Subject: [PATCH 10/73] Version 4.14.14. - Fix webview regression on Linux X11. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 4 ++-- Telegram/build/version | 8 ++++---- Telegram/lib_webview | 2 +- changelog.txt | 4 ++++ 7 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index c3069c310..f201ce5b8 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.14.14.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 5672ca4c7..ef7ff18bd 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,14,13,0 - PRODUCTVERSION 4,14,13,0 + FILEVERSION 4,14,14,0 + PRODUCTVERSION 4,14,14,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "4.14.13.0" + VALUE "FileVersion", "4.14.14.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.14.13.0" + VALUE "ProductVersion", "4.14.14.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 0e56d18d2..2dcf9cd4f 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,14,13,0 - PRODUCTVERSION 4,14,13,0 + FILEVERSION 4,14,14,0 + PRODUCTVERSION 4,14,14,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "4.14.13.0" + VALUE "FileVersion", "4.14.14.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.14.13.0" + VALUE "ProductVersion", "4.14.14.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index eb7c2b228..a7c371978 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs; constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs; constexpr auto AppName = "Telegram Desktop"_cs; constexpr auto AppFile = "Telegram"_cs; -constexpr auto AppVersion = 4014013; -constexpr auto AppVersionStr = "4.14.13"; +constexpr auto AppVersion = 4014014; +constexpr auto AppVersionStr = "4.14.14"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 3340735be..4110ac17c 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 4014013 +AppVersion 4014014 AppVersionStrMajor 4.14 -AppVersionStrSmall 4.14.13 -AppVersionStr 4.14.13 +AppVersionStrSmall 4.14.14 +AppVersionStr 4.14.14 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 4.14.13 +AppVersionOriginal 4.14.14 diff --git a/Telegram/lib_webview b/Telegram/lib_webview index db36c9935..75cc4c04d 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit db36c99359b0c1cb376c8fa6f5f7ab80807eb45f +Subproject commit 75cc4c04d28a09172b1df596913c09161aaede35 diff --git a/changelog.txt b/changelog.txt index ee66dacaf..98d342585 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +4.14.14 (09.02.24) + +- Fix webview regression on Linux X11. + 4.14.13 (02.02.24) - Fix display of statistics for single posts. From e5f90cd40d552674d7943f8f422c340203909658 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 10 Feb 2024 00:32:40 +0400 Subject: [PATCH 11/73] Version 4.14.15. - Fix webview regression on Linux X11. (2nd attempt) --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 4 ++-- Telegram/build/version | 8 ++++---- Telegram/lib_webview | 2 +- changelog.txt | 4 ++++ 7 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index f201ce5b8..276d6be2f 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.14.15.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index ef7ff18bd..452e4e8c0 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,14,14,0 - PRODUCTVERSION 4,14,14,0 + FILEVERSION 4,14,15,0 + PRODUCTVERSION 4,14,15,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "4.14.14.0" + VALUE "FileVersion", "4.14.15.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.14.14.0" + VALUE "ProductVersion", "4.14.15.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 2dcf9cd4f..d4dfd203f 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,14,14,0 - PRODUCTVERSION 4,14,14,0 + FILEVERSION 4,14,15,0 + PRODUCTVERSION 4,14,15,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "4.14.14.0" + VALUE "FileVersion", "4.14.15.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.14.14.0" + VALUE "ProductVersion", "4.14.15.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index a7c371978..dfd280a09 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs; constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs; constexpr auto AppName = "Telegram Desktop"_cs; constexpr auto AppFile = "Telegram"_cs; -constexpr auto AppVersion = 4014014; -constexpr auto AppVersionStr = "4.14.14"; +constexpr auto AppVersion = 4014015; +constexpr auto AppVersionStr = "4.14.15"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 4110ac17c..17d7d81b8 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 4014014 +AppVersion 4014015 AppVersionStrMajor 4.14 -AppVersionStrSmall 4.14.14 -AppVersionStr 4.14.14 +AppVersionStrSmall 4.14.15 +AppVersionStr 4.14.15 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 4.14.14 +AppVersionOriginal 4.14.15 diff --git a/Telegram/lib_webview b/Telegram/lib_webview index 75cc4c04d..db36c9935 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit 75cc4c04d28a09172b1df596913c09161aaede35 +Subproject commit db36c99359b0c1cb376c8fa6f5f7ab80807eb45f diff --git a/changelog.txt b/changelog.txt index 98d342585..e2ecb5f98 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +4.14.15 (10.02.24) + +- Fix webview regression on Linux X11. (2nd attempt) + 4.14.14 (09.02.24) - Fix webview regression on Linux X11. From 00f98793b1402d7eebde919ec48d1b031f9011c3 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 12 Feb 2024 16:18:44 +0400 Subject: [PATCH 12/73] Update lib_webview --- Telegram/lib_webview | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_webview b/Telegram/lib_webview index db36c9935..18f9560c9 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit db36c99359b0c1cb376c8fa6f5f7ab80807eb45f +Subproject commit 18f9560c980f8799e24653efb9c6ec23ef0c9232 From c5db2b81752426302396906cbab2c417ac1b9b42 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 14 Feb 2024 01:14:01 +0400 Subject: [PATCH 13/73] Ensure custom arguments get applied in generated .desktop file --- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 6c1690c18..a63c54bc7 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -280,6 +280,11 @@ bool GenerateDesktopFile( } } + if (!args.isEmpty() + && target->has_key("Desktop Entry", "DBusActivatable")) { + target->remove_key("Desktop Entry", "DBusActivatable"); + } + target->save_to_file(targetFile.toStdString()); } catch (const std::exception &e) { if (!silent) { From 8b6a7a443ed675b9bc61142c1401b481fba4f629 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 14 Feb 2024 08:39:50 +0400 Subject: [PATCH 14/73] Switch from libproxy to GProxyResolver --- Telegram/build/docker/centos_env/Dockerfile | 19 +------------------ cmake | 2 +- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index b3d1a0650..5e96e3340 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -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 61a1f5871bc83ec0514f867960616f45e142297c \ + && git fetch --depth=1 origin 7f880df36a926fa2eeb4ef473261f2ca09d3c135 \ && git reset --hard FETCH_HEAD \ && rm -rf .git @@ -109,20 +109,6 @@ RUN git clone -b v21.9 --depth=1 --recursive {{ GIT }}/protocolbuffers/protobuf. && cmake --build build --parallel \ && rm -rf .git -FROM patches AS libproxy -RUN git clone -b 0.4.18 --depth=1 {{ GIT }}/libproxy/libproxy.git \ - && cd libproxy \ - && git apply ../patches/libproxy.patch \ - && cmake -GNinja -B build . \ - -DCMAKE_BUILD_TYPE=None \ - -DWITH_DBUS=OFF \ - -DWITH_NM=OFF \ - -DWITH_NMold=OFF \ - && cmake --build build --parallel \ - && DESTDIR="{{ LibrariesPath }}/libproxy-cache" cmake --install build \ - && cd .. \ - && rm -rf libproxy - FROM builder AS lcms2 RUN git clone -b lcms2.15 --depth=1 {{ GIT }}/mm2/Little-CMS.git \ && cd Little-CMS \ @@ -753,7 +739,6 @@ RUN git clone -b 1.78.1 --depth=1 {{ GIT }}/GNOME/gobject-introspection.git \ FROM patches AS qt COPY --link --from=zlib {{ LibrariesPath }}/zlib-cache / -COPY --link --from=libproxy {{ LibrariesPath }}/libproxy-cache / COPY --link --from=lcms2 {{ LibrariesPath }}/lcms2-cache / COPY --link --from=mozjpeg {{ LibrariesPath }}/mozjpeg-cache / COPY --link --from=xcb {{ LibrariesPath }}/xcb-cache / @@ -782,7 +767,6 @@ RUN git clone -b {{ QT_TAG }} --depth=1 {{ GIT }}/qt/qt5.git qt_{{ QT }} \ CMAKE_BUILD_TYPE=None \ -opensource \ -confirm-license \ - -libproxy \ -qt-libpng \ -qt-harfbuzz \ -qt-pcre \ @@ -856,7 +840,6 @@ FROM builder-base COPY --link --from=zlib {{ LibrariesPath }}/zlib-cache / COPY --link --from=xz {{ LibrariesPath }}/xz-cache / COPY --link --from=protobuf {{ LibrariesPath }}/protobuf protobuf -COPY --link --from=libproxy {{ LibrariesPath }}/libproxy-cache / COPY --link --from=lcms2 {{ LibrariesPath }}/lcms2-cache / COPY --link --from=brotli {{ LibrariesPath }}/brotli-cache / COPY --link --from=highway {{ LibrariesPath }}/highway-cache / diff --git a/cmake b/cmake index a32690808..a46279fcf 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit a32690808bd91c15206524eb7fd3a346bf51b149 +Subproject commit a46279fcfe69ebcc806bb31679ccece5f7c07508 From dcf4f45a36902384fb9f73caca69a44951285c0f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 14 Feb 2024 17:20:11 +0300 Subject: [PATCH 15/73] Fixed ability to copy entire translated text from context menu. --- Telegram/SourceFiles/history/history_item.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 15fbde7b5..5da6e880f 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -2665,7 +2665,7 @@ TextWithEntities HistoryItem::translatedTextWithLocalEntities() const { TextForMimeData HistoryItem::clipboardText() const { return isService() ? TextForMimeData() - : TextForMimeData::WithExpandedLinks(_text); + : TextForMimeData::WithExpandedLinks(translatedText()); } bool HistoryItem::changeViewsCount(int count) { From 113c8a797fbcf5125ffaca1b47b0db9c7aa45e29 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 14 Feb 2024 18:26:18 +0300 Subject: [PATCH 16/73] Added fade effect to input message fields. --- .../chat_helpers/chat_helpers.style | 1 + .../chat_helpers/message_field.cpp | 69 +++++++++++++++++++ .../SourceFiles/chat_helpers/message_field.h | 2 + .../SourceFiles/history/history_widget.cpp | 1 + .../history_view_compose_controls.cpp | 1 + 5 files changed, 74 insertions(+) diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 21f1ad3c3..14af86253 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -968,6 +968,7 @@ historyComposeField: InputField(defaultInputField) { duration: 100; } historyComposeFieldMaxHeight: 224px; +historyComposeFieldFadeHeight: 8px; // historyMinHeight: 56px; historyAttach: IconButton(defaultIconButton) { diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index ee3418394..77ca3bdd3 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/qthelp_url.h" #include "base/event_filter.h" #include "ui/layers/generic_box.h" +#include "ui/rect.h" #include "core/shortcuts.h" #include "core/application.h" #include "core/core_settings.h" @@ -442,6 +443,74 @@ bool HasSendText(not_null field) { return false; } +void InitMessageFieldFade(not_null field) { + class Fade final : public Ui::RpWidget { + public: + using Ui::RpWidget::RpWidget; + + void setFade(QPixmap &&fade) { + _fade = std::move(fade); + } + + int resizeGetHeight(int newWidth) override { + return st::historyComposeFieldFadeHeight; + } + + private: + void paintEvent(QPaintEvent *event) override { + auto p = QPainter(this); + p.drawTiledPixmap(rect(), _fade); + } + + QPixmap _fade; + + }; + + const auto topFade = Ui::CreateChild(field.get()); + const auto bottomFade = Ui::CreateChild(field.get()); + + const auto generateFade = [=] { + const auto size = QSize(1, st::historyComposeFieldFadeHeight); + auto fade = QPixmap(size * style::DevicePixelRatio()); + fade.setDevicePixelRatio(style::DevicePixelRatio()); + fade.fill(Qt::transparent); + { + auto p = QPainter(&fade); + + auto gradient = QLinearGradient(0, 1, 0, size.height()); + gradient.setStops({ + { 0., st::historyComposeField.textBg->c }, + { .9, Qt::transparent }, + }); + p.setPen(Qt::NoPen); + p.setBrush(gradient); + p.drawRect(Rect(size)); + } + bottomFade->setFade(fade.transformed(QTransform().scale(1, -1))); + topFade->setFade(std::move(fade)); + }; + generateFade(); + style::PaletteChanged( + ) | rpl::start_with_next([=] { + generateFade(); + }, topFade->lifetime()); + + field->sizeValue( + ) | rpl::start_with_next_done([=](const QSize &size) { + topFade->resizeToWidth(size.width()); + bottomFade->resizeToWidth(size.width()); + bottomFade->move( + 0, + size.height() - st::historyComposeFieldFadeHeight); + }, [t = Ui::MakeWeak(topFade), b = Ui::MakeWeak(bottomFade)] { + Ui::DestroyChild(t.data()); + Ui::DestroyChild(b.data()); + }, topFade->lifetime()); + + topFade->show(); + bottomFade->show(); +} + InlineBotQuery ParseInlineBotQuery( not_null session, not_null field) { diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index 7bce7facf..18ad42c09 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -79,6 +79,8 @@ void InitSpellchecker( bool HasSendText(not_null field); +void InitMessageFieldFade(not_null field); + struct InlineBotQuery { QString query; QString username; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 4921e6307..5fa7a5a19 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -398,6 +398,7 @@ HistoryWidget::HistoryWidget( showPremiumToast(document); return false; }); + InitMessageFieldFade(_field); _keyboard->sendCommandRequests( ) | rpl::start_with_next([=](Bot::SendCommandRequest r) { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 05675a2fe..ada3c7295 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1575,6 +1575,7 @@ void ComposeControls::initField() { } return false; }); + InitMessageFieldFade(_field); _field->setEditLinkCallback( DefaultEditLinkCallback(_show, _field, &_st.boxField)); initAutocomplete(); From 5cd0d82ffbfc77d33d0fa08faba7aeae1c8d2ca7 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 14 Feb 2024 19:57:39 +0300 Subject: [PATCH 17/73] Added counter label of characters limit for long media captions. --- Telegram/CMakeLists.txt | 2 - .../chat_helpers/chat_helpers.style | 7 +++ .../SourceFiles/history/history_widget.cpp | 58 ++++++++++++++++--- Telegram/SourceFiles/history/history_widget.h | 6 ++ .../history_view_characters_limit.cpp | 35 +++++++++++ .../controls/history_view_characters_limit.h | 24 ++++++++ .../history_view_compose_controls.cpp | 54 +++++++++++++++++ .../controls/history_view_compose_controls.h | 6 ++ .../view/history_view_replies_section.cpp | 9 +-- .../view/history_view_scheduled_section.cpp | 9 +-- Telegram/cmake/td_ui.cmake | 4 ++ 11 files changed, 188 insertions(+), 26 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/controls/history_view_characters_limit.cpp create mode 100644 Telegram/SourceFiles/history/view/controls/history_view_characters_limit.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index b8765cd63..ac17eb8a5 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -676,8 +676,6 @@ PRIVATE history/view/controls/history_view_ttl_button.h history/view/controls/history_view_voice_record_bar.cpp history/view/controls/history_view_voice_record_bar.h - history/view/controls/history_view_voice_record_button.cpp - history/view/controls/history_view_voice_record_button.h history/view/controls/history_view_webpage_processor.cpp history/view/controls/history_view_webpage_processor.h history/view/media/history_view_call.cpp diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 14af86253..135b895ed 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1071,6 +1071,13 @@ historyMessagesTTLLabel: FlatLabel(defaultFlatLabel) { textFg: windowSubTextFg; } +historyCharsLimitationLabel: FlatLabel(defaultFlatLabel) { + // The same as a width of the historySendSize. + minWidth: 44px; + align: align(center); + textFg: attentionButtonFg; +} + historyRecordVoiceFg: historyComposeIconFg; historyRecordVoiceFgOver: historyComposeIconFgOver; historyRecordVoiceFgInactive: attentionButtonFg; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 5fa7a5a19..c713d464a 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -71,6 +71,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "data/data_histories.h" #include "data/data_group_call.h" +#include "data/data_peer_values.h" // Data::AmPremiumValue. +#include "data/data_premium_limits.h" // Data::PremiumLimits. #include "data/stickers/data_stickers.h" #include "data/stickers/data_custom_emoji.h" #include "history/history.h" @@ -80,6 +82,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_inner_widget.h" #include "history/history_item_components.h" #include "history/history_unread_things.h" +#include "history/view/controls/history_view_characters_limit.h" #include "history/view/controls/history_view_compose_search.h" #include "history/view/controls/history_view_forward_panel.h" #include "history/view/controls/history_view_draft_options.h" @@ -1622,6 +1625,8 @@ void HistoryWidget::fieldChanged() { } }); + checkCharsLimitation(); + updateSendButtonType(); if (!HasSendText(_field)) { _fieldIsEmpty = true; @@ -3836,6 +3841,18 @@ void HistoryWidget::windowIsVisibleChanged() { }); } +TextWithEntities HistoryWidget::prepareTextForEditMsg() const { + const auto textWithTags = _field->getTextWithAppliedMarkdown(); + const auto prepareFlags = Ui::ItemTextOptions( + _history, + session().user()).flags; + auto left = TextWithEntities { + textWithTags.text, + TextUtilities::ConvertTextTagsToEntities(textWithTags.tags) }; + TextUtilities::PrepareForSending(left, prepareFlags); + return left; +} + void HistoryWidget::saveEditMsg() { Expects(_history != nullptr); @@ -3849,15 +3866,8 @@ void HistoryWidget::saveEditMsg() { return; } const auto webPageDraft = _preview->draft(); - const auto textWithTags = _field->getTextWithAppliedMarkdown(); - const auto prepareFlags = Ui::ItemTextOptions( - _history, - session().user()).flags; + auto left = prepareTextForEditMsg(); auto sending = TextWithEntities(); - auto left = TextWithEntities { - textWithTags.text, - TextUtilities::ConvertTextTagsToEntities(textWithTags.tags) }; - TextUtilities::PrepareForSending(left, prepareFlags); const auto media = item->media(); if (!TextUtilities::CutPart(sending, left, MaxMessageSize) @@ -7291,6 +7301,36 @@ void HistoryWidget::showPremiumToast(not_null document) { _stickerToast->showFor(document); } +void HistoryWidget::checkCharsLimitation() { + if (!_history || !_editMsgId) { + if (_charsLimitation) { + _charsLimitation = nullptr; + } + return; + } + const auto limits = Data::PremiumLimits(&session()); + const auto left = prepareTextForEditMsg(); + const auto remove = left.text.size() - limits.captionLengthCurrent(); + if (remove > 0) { + if (!_charsLimitation) { + _charsLimitation = base::make_unique_q( + this, + _send.get()); + _charsLimitation->show(); + Data::AmPremiumValue( + &session() + ) | rpl::start_with_next([=] { + checkCharsLimitation(); + }, _charsLimitation->lifetime()); + } + _charsLimitation->setLeft(remove); + } else { + if (_charsLimitation) { + _charsLimitation = nullptr; + } + } +} + void HistoryWidget::setFieldText( const TextWithTags &textWithTags, TextUpdateEvents events, @@ -7303,6 +7343,8 @@ void HistoryWidget::setFieldText( _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; + checkCharsLimitation(); + if (_preview) { _preview->checkNow(false); } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 3fa34ac2b..e4ec63ea2 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -101,6 +101,7 @@ class VoiceRecordBar; class ForwardPanel; class TTLButton; class WebpageProcessor; +class CharactersLimitLabel; } // namespace HistoryView::Controls class BotKeyboard; @@ -543,6 +544,7 @@ private: [[nodiscard]] bool insideJumpToEndInsteadOfToUnread() const; void createUnreadBarAndResize(); + [[nodiscard]] TextWithEntities prepareTextForEditMsg() const; void saveEditMsg(); void setupPreview(); @@ -643,6 +645,8 @@ private: void switchToSearch(QString query); + void checkCharsLimitation(); + MTP::Sender _api; FullReplyTo _replyTo; Ui::Text::String _replyToName; @@ -763,6 +767,8 @@ private: object_ptr _field; base::unique_qptr _fieldDisabled; base::unique_qptr _sendRestriction; + using CharactersLimitLabel = HistoryView::Controls::CharactersLimitLabel; + base::unique_qptr _charsLimitation; QString _sendRestrictionKey; Ui::Animations::Simple _inPhotoEditOver; bool _inDetails = false; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.cpp b/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.cpp new file mode 100644 index 000000000..8fa5621c6 --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.cpp @@ -0,0 +1,35 @@ +/* +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/controls/history_view_characters_limit.h" + +#include "styles/style_chat_helpers.h" + +namespace HistoryView::Controls { + +CharactersLimitLabel::CharactersLimitLabel( + not_null parent, + not_null widgetBelow) +: Ui::FlatLabel(parent, st::historyCharsLimitationLabel) { + rpl::combine( + Ui::RpWidget::heightValue(), + widgetBelow->positionValue() + ) | rpl::start_with_next([=](int height, const QPoint &p) { + move(p.x(), p.y() - height); + }, lifetime()); +} + +void CharactersLimitLabel::setLeft(int value) { + if (value <= 0) { + return; + } + constexpr auto kMinus = QChar(0x2212); + constexpr auto kLimit = int(999); + Ui::FlatLabel::setText(kMinus + QString::number(std::min(value, kLimit))); +} + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.h b/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.h new file mode 100644 index 000000000..b16b6bfa1 --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.h @@ -0,0 +1,24 @@ +/* +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 "ui/widgets/labels.h" + +namespace HistoryView::Controls { + +class CharactersLimitLabel final : public Ui::FlatLabel { +public: + CharactersLimitLabel( + not_null parent, + not_null widgetBelow); + + void setLeft(int value); + +}; + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index ada3c7295..9122d0674 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_forum_topic.h" #include "data/data_peer_values.h" #include "data/data_photo_media.h" +#include "data/data_premium_limits.h" // Data::PremiumLimits. #include "data/stickers/data_stickers.h" #include "data/stickers/data_custom_emoji.h" #include "data/data_web_page.h" @@ -47,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/power_saving.h" #include "history/history.h" #include "history/history_item.h" +#include "history/view/controls/history_view_characters_limit.h" #include "history/view/controls/history_view_forward_panel.h" #include "history/view/controls/history_view_draft_options.h" #include "history/view/controls/history_view_voice_record_bar.h" @@ -63,6 +65,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/audio/media_audio_capture.h" #include "media/audio/media_audio.h" #include "settings/settings_premium.h" +#include "ui/item_text_options.h" #include "ui/text/text_options.h" #include "ui/text/text_utilities.h" #include "ui/ui_utility.h" @@ -1205,6 +1208,8 @@ void ComposeControls::setFieldText( _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; + checkCharsLimitation(); + if (_preview) { _preview->checkNow(false); } @@ -1788,6 +1793,8 @@ void ComposeControls::fieldChanged() { } }); + checkCharsLimitation(); + _saveCloudDraftTimer.cancel(); if (!(_textUpdateEvents & TextUpdateEvent::SaveDraft)) { return; @@ -2021,6 +2028,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { _preview->setDisabled(false); } } + checkCharsLimitation(); } void ComposeControls::cancelForward() { @@ -3284,4 +3292,50 @@ Fn ComposeControls::restoreTextCallback( }); } +TextWithEntities ComposeControls::prepareTextForEditMsg() const { + if (!_history) { + return {}; + } + const auto textWithTags = getTextWithAppliedMarkdown(); + const auto prepareFlags = Ui::ItemTextOptions( + _history, + session().user()).flags; + auto left = TextWithEntities { + textWithTags.text, + TextUtilities::ConvertTextTagsToEntities(textWithTags.tags) }; + TextUtilities::PrepareForSending(left, prepareFlags); + return left; +} + +void ComposeControls::checkCharsLimitation() { + if (!_history || !isEditingMessage()) { + if (_charsLimitation) { + _charsLimitation = nullptr; + } + return; + } + const auto limits = Data::PremiumLimits(&session()); + const auto left = prepareTextForEditMsg(); + const auto remove = left.text.size() - limits.captionLengthCurrent(); + if (remove > 0) { + if (!_charsLimitation) { + using namespace Controls; + _charsLimitation = base::make_unique_q( + _wrap.get(), + _send.get()); + _charsLimitation->show(); + Data::AmPremiumValue( + &session() + ) | rpl::start_with_next([=] { + checkCharsLimitation(); + }, _charsLimitation->lifetime()); + } + _charsLimitation->setLeft(remove); + } else { + if (_charsLimitation) { + _charsLimitation = nullptr; + } + } +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 795e25760..4911bfde9 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -83,6 +83,7 @@ namespace HistoryView::Controls { class VoiceRecordBar; class TTLButton; class WebpageProcessor; +class CharactersLimitLabel; } // namespace HistoryView::Controls namespace HistoryView { @@ -228,6 +229,8 @@ public: [[nodiscard]] rpl::producer fieldMenuShownValue() const; [[nodiscard]] not_null likeAnimationTarget() const; + [[nodiscard]] TextWithEntities prepareTextForEditMsg() const; + void applyCloudDraft(); void applyDraft( FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); @@ -334,6 +337,8 @@ private: void registerDraftSource(); void changeFocusedControl(); + void checkCharsLimitation(); + const style::ComposeControls &_st; const ChatHelpers::ComposeFeatures _features; const not_null _parent; @@ -373,6 +378,7 @@ private: std::unique_ptr _sendAs; std::unique_ptr _silent; std::unique_ptr _ttlInfo; + base::unique_qptr _charsLimitation; std::unique_ptr _inlineResults; std::unique_ptr _tabbedPanel; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index a244437ff..25e174924 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1229,16 +1229,9 @@ void RepliesWidget::edit( if (*saveEditMsgRequestId) { return; } - const auto textWithTags = _composeControls->getTextWithAppliedMarkdown(); const auto webpage = _composeControls->webPageDraft(); - const auto prepareFlags = Ui::ItemTextOptions( - _history, - session().user()).flags; auto sending = TextWithEntities(); - auto left = TextWithEntities { - textWithTags.text, - TextUtilities::ConvertTextTagsToEntities(textWithTags.tags) }; - TextUtilities::PrepareForSending(left, prepareFlags); + auto left = _composeControls->prepareTextForEditMsg(); if (!TextUtilities::CutPart(sending, left, MaxMessageSize) && (!item diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index ca81ba290..8c83ef478 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -649,16 +649,9 @@ void ScheduledWidget::edit( if (*saveEditMsgRequestId) { return; } - const auto textWithTags = _composeControls->getTextWithAppliedMarkdown(); const auto webpage = _composeControls->webPageDraft(); - const auto prepareFlags = Ui::ItemTextOptions( - _history, - session().user()).flags; auto sending = TextWithEntities(); - auto left = TextWithEntities { - textWithTags.text, - TextUtilities::ConvertTextTagsToEntities(textWithTags.tags) }; - TextUtilities::PrepareForSending(left, prepareFlags); + auto left = _composeControls->prepareTextForEditMsg(); if (!TextUtilities::CutPart(sending, left, MaxMessageSize) && (!item diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 53db41bc6..817f3c9ab 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -100,6 +100,10 @@ PRIVATE history/history_view_top_toast.cpp history/history_view_top_toast.h + history/view/controls/history_view_characters_limit.cpp + history/view/controls/history_view_characters_limit.h + history/view/controls/history_view_voice_record_button.cpp + history/view/controls/history_view_voice_record_button.h info/profile/info_profile_icon.cpp info/profile/info_profile_icon.h From d1eaf284b163827df3d445bbef12530fd8fd0467 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 15 Feb 2024 08:16:16 +0300 Subject: [PATCH 18/73] Fixed text suggestion in toast when media caption is too long. --- Telegram/SourceFiles/history/history_widget.cpp | 8 ++++++-- .../history/view/history_view_replies_section.cpp | 8 ++++++-- .../history/view/history_view_scheduled_section.cpp | 8 ++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index c713d464a..fbc4aa1e2 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -3870,7 +3870,11 @@ void HistoryWidget::saveEditMsg() { auto sending = TextWithEntities(); const auto media = item->media(); - if (!TextUtilities::CutPart(sending, left, MaxMessageSize) + + const auto originalLeftSize = left.text.size(); + const auto maxCaptionSize = Data::PremiumLimits( + &session()).captionLengthCurrent(); + if (!TextUtilities::CutPart(sending, left, maxCaptionSize) && (webPageDraft.removed || webPageDraft.url.isEmpty() || !webPageDraft.manual) @@ -3880,7 +3884,7 @@ void HistoryWidget::saveEditMsg() { Box(item, suggestModerateActions)); return; } else if (!left.text.isEmpty()) { - const auto remove = left.text.size(); + const auto remove = originalLeftSize - maxCaptionSize; controller()->showToast( tr::lng_edit_limit_reached(tr::now, lt_count, remove)); return; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 25e174924..ef330f374 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -79,6 +79,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_shared_media.h" #include "data/data_send_action.h" +#include "data/data_premium_limits.h" #include "storage/storage_media_prepare.h" #include "storage/storage_shared_media.h" #include "storage/storage_account.h" @@ -1233,7 +1234,10 @@ void RepliesWidget::edit( auto sending = TextWithEntities(); auto left = _composeControls->prepareTextForEditMsg(); - if (!TextUtilities::CutPart(sending, left, MaxMessageSize) + const auto originalLeftSize = left.text.size(); + const auto maxCaptionSize = Data::PremiumLimits( + &session()).captionLengthCurrent(); + if (!TextUtilities::CutPart(sending, left, maxCaptionSize) && (!item || !item->media() || !item->media()->allowsEditCaption())) { @@ -1244,7 +1248,7 @@ void RepliesWidget::edit( } return; } else if (!left.text.isEmpty()) { - const auto remove = left.text.size(); + const auto remove = originalLeftSize - maxCaptionSize; controller()->showToast( tr::lng_edit_limit_reached(tr::now, lt_count, remove)); return; diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 8c83ef478..581de48a5 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_message_reactions.h" #include "data/data_peer_values.h" +#include "data/data_premium_limits.h" #include "storage/storage_media_prepare.h" #include "storage/storage_account.h" #include "storage/localimageloader.h" @@ -653,7 +654,10 @@ void ScheduledWidget::edit( auto sending = TextWithEntities(); auto left = _composeControls->prepareTextForEditMsg(); - if (!TextUtilities::CutPart(sending, left, MaxMessageSize) + const auto originalLeftSize = left.text.size(); + const auto maxCaptionSize = Data::PremiumLimits( + &session()).captionLengthCurrent(); + if (!TextUtilities::CutPart(sending, left, maxCaptionSize) && (!item || !item->media() || !item->media()->allowsEditCaption())) { @@ -664,7 +668,7 @@ void ScheduledWidget::edit( } return; } else if (!left.text.isEmpty()) { - const auto remove = left.text.size(); + const auto remove = originalLeftSize - maxCaptionSize; controller()->showToast( tr::lng_edit_limit_reached(tr::now, lt_count, remove)); return; From 6de471db1779f05eba1ea3a6d38d474dd4d33700 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 15 Feb 2024 09:37:11 +0300 Subject: [PATCH 19/73] Added counter label of characters limit to send files box. --- Telegram/SourceFiles/boxes/send_files_box.cpp | 35 +++++++++++++++++++ Telegram/SourceFiles/boxes/send_files_box.h | 8 +++++ .../SourceFiles/history/history_widget.cpp | 3 +- .../history_view_characters_limit.cpp | 20 ++++++++--- .../controls/history_view_characters_limit.h | 3 +- .../history_view_compose_controls.cpp | 3 +- 6 files changed, 64 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 18f02439c..7cd37dbe8 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/tabbed_selector.h" #include "editor/photo_editor_layer_widget.h" #include "history/history_drag_area.h" +#include "history/view/controls/history_view_characters_limit.h" #include "history/view/history_view_schedule_box.h" #include "core/file_utilities.h" #include "core/mime_type.h" @@ -48,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lottie/lottie_single_player.h" #include "data/data_document.h" #include "data/data_user.h" +#include "data/data_peer_values.h" // Data::AmPremiumValue. #include "data/data_premium_limits.h" #include "data/stickers/data_stickers.h" #include "data/stickers/data_custom_emoji.h" @@ -1060,6 +1062,39 @@ void SendFilesBox::setupCaption() { updateCaptionPlaceholder(); setupEmojiPanel(); + + rpl::single(rpl::empty_value()) | rpl::then( + _caption->changes() + ) | rpl::start_with_next([=] { + checkCharsLimitation(); + }, _caption->lifetime()); +} + +void SendFilesBox::checkCharsLimitation() { + const auto limits = Data::PremiumLimits(&_show->session()); + const auto caption = (_caption && !_caption->isHidden()) + ? _caption->getTextWithAppliedMarkdown() + : TextWithTags(); + const auto remove = caption.text.size() - limits.captionLengthCurrent(); + if ((remove > 0) && _emojiToggle) { + if (!_charsLimitation) { + _charsLimitation = base::make_unique_q( + this, + _emojiToggle.data(), + style::al_top); + _charsLimitation->show(); + Data::AmPremiumValue( + &_show->session() + ) | rpl::start_with_next([=] { + checkCharsLimitation(); + }, _charsLimitation->lifetime()); + } + _charsLimitation->setLeft(remove); + } else { + if (_charsLimitation) { + _charsLimitation = nullptr; + } + } } void SendFilesBox::setupEmojiPanel() { diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index b1db58787..0acbb8f49 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -50,6 +50,10 @@ namespace SendMenu { enum class Type; } // namespace SendMenu +namespace HistoryView::Controls { +class CharactersLimitLabel; +} // namespace HistoryView::Controls + enum class SendFilesAllow { OnlyOne = (1 << 0), Photos = (1 << 1), @@ -221,6 +225,8 @@ private: void enqueueNextPrepare(); void addPreparedAsyncFile(Ui::PreparedFile &&file); + void checkCharsLimitation(); + const std::shared_ptr _show; const style::ComposeControls &_st; const Api::SendType _sendType = Api::SendType(); @@ -244,6 +250,8 @@ private: object_ptr _emojiToggle = { nullptr }; base::unique_qptr _emojiPanel; base::unique_qptr _emojiFilter; + using CharactersLimitLabel = HistoryView::Controls::CharactersLimitLabel; + base::unique_qptr _charsLimitation; object_ptr _groupFiles = { nullptr }; object_ptr _sendImagesAsPhotos = { nullptr }; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index fbc4aa1e2..e003b6020 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -7319,7 +7319,8 @@ void HistoryWidget::checkCharsLimitation() { if (!_charsLimitation) { _charsLimitation = base::make_unique_q( this, - _send.get()); + _send.get(), + style::al_bottom); _charsLimitation->show(); Data::AmPremiumValue( &session() diff --git a/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.cpp b/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.cpp index 8fa5621c6..c3f273674 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.cpp @@ -7,20 +7,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/controls/history_view_characters_limit.h" +#include "ui/rect.h" #include "styles/style_chat_helpers.h" namespace HistoryView::Controls { CharactersLimitLabel::CharactersLimitLabel( not_null parent, - not_null widgetBelow) + not_null widgetToAlign, + style::align align) : Ui::FlatLabel(parent, st::historyCharsLimitationLabel) { + Expects((align == style::al_top) || align == style::al_bottom); + const auto w = st::historyCharsLimitationLabel.minWidth; + using F = Fn; + const auto position = (align == style::al_top) + ? F([=](int height, const QRect &g) { + move(g.x() + (g.width() - w) / 2, rect::bottom(g)); + }) + : F([=](int height, const QRect &g) { + move(g.x() + (g.width() - w) / 2, g.y() - height); + }); rpl::combine( Ui::RpWidget::heightValue(), - widgetBelow->positionValue() - ) | rpl::start_with_next([=](int height, const QPoint &p) { - move(p.x(), p.y() - height); - }, lifetime()); + widgetToAlign->geometryValue() + ) | rpl::start_with_next(position, lifetime()); } void CharactersLimitLabel::setLeft(int value) { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.h b/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.h index b16b6bfa1..3b6054e19 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.h @@ -15,7 +15,8 @@ class CharactersLimitLabel final : public Ui::FlatLabel { public: CharactersLimitLabel( not_null parent, - not_null widgetBelow); + not_null widgetToAlign, + style::align align); void setLeft(int value); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 9122d0674..19ec21b33 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -3322,7 +3322,8 @@ void ComposeControls::checkCharsLimitation() { using namespace Controls; _charsLimitation = base::make_unique_q( _wrap.get(), - _send.get()); + _send.get(), + style::al_bottom); _charsLimitation->show(); Data::AmPremiumValue( &session() From c891ee3a459a59973351f98d8629f4e670719ecb Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 15 Feb 2024 13:49:06 +0400 Subject: [PATCH 20/73] Fix build on Windows. --- .../history/view/controls/history_view_voice_record_button.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp index 4dbc3cd10..84d565639 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp @@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_chat_helpers.h" #include "styles/style_layers.h" +#include + namespace HistoryView::Controls { namespace { From e32cbf468b0fbc5069d713daefb0609a9f7ab571 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 2 Feb 2024 13:40:01 +0400 Subject: [PATCH 21/73] Update API scheme to layer 174. --- Telegram/Resources/langs/lang.strings | 5 ++ Telegram/SourceFiles/api/api_updates.cpp | 2 + .../data/data_scheduled_messages.cpp | 2 + Telegram/SourceFiles/data/data_session.cpp | 1 + .../export/data/export_data_types.cpp | 4 ++ .../export/data/export_data_types.h | 7 ++- .../export/output/export_output_html.cpp | 5 ++ .../export/output/export_output_json.cpp | 4 ++ .../admin_log/history_admin_log_item.cpp | 47 +++++++++++++++++++ Telegram/SourceFiles/history/history_item.cpp | 15 ++++++ Telegram/SourceFiles/mtproto/scheme/api.tl | 12 +++-- .../settings/settings_privacy_controllers.cpp | 1 + 12 files changed, 100 insertions(+), 5 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 601366ee4..7b57efbe0 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1741,6 +1741,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_giveaway_results#other" = "{count} winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes."; "lng_action_giveaway_results_some" = "Some winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes."; "lng_action_giveaway_results_none" = "No winners of the giveaway could be selected."; +"lng_action_boost_apply#one" = "{from} boosted the group."; +"lng_action_boost_apply#other" = "{from} boosted the group {count} times."; "lng_similar_channels_title" = "Similar channels"; "lng_similar_channels_view_all" = "View all"; @@ -3748,6 +3750,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_changed_stickers_group" = "{from} changed the group's {sticker_set}"; "lng_admin_log_changed_stickers_set" = "sticker set"; "lng_admin_log_removed_stickers_group" = "{from} removed the group's sticker set"; +"lng_admin_log_changed_emoji_group" = "{from} changed the group's {sticker_set}"; +"lng_admin_log_changed_emoji_set" = "emoji set"; +"lng_admin_log_removed_emoji_group" = "{from} removed the group's emoji set"; "lng_admin_log_changed_linked_chat" = "{from} changed the discussion group to «{chat}»"; "lng_admin_log_removed_linked_chat" = "{from} removed the discussion group"; "lng_admin_log_changed_linked_channel" = "{from} changed the linked channel to «{chat}»"; diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index dc8eb1593..2b0450374 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -1114,6 +1114,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { (d.is_out() ? peerToMTP(_session->userPeerId()) : MTP_peerUser(d.vuser_id())), + MTPint(), // from_boosts_applied MTP_peerUser(d.vuser_id()), MTPPeer(), // saved_peer_id d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(), @@ -1146,6 +1147,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { MTP_flags(flags), d.vid(), MTP_peerUser(d.vfrom_id()), + MTPint(), // from_boosts_applied MTP_peerChat(d.vchat_id()), MTPPeer(), // saved_peer_id d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(), diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.cpp b/Telegram/SourceFiles/data/data_scheduled_messages.cpp index f122f05b9..9ea40996e 100644 --- a/Telegram/SourceFiles/data/data_scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/data_scheduled_messages.cpp @@ -67,6 +67,7 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000); MTP_flags(data.vflags().v | MTPDmessage::Flag::f_from_scheduled), data.vid(), data.vfrom_id() ? *data.vfrom_id() : MTPPeer(), + MTPint(), // from_boosts_applied data.vpeer_id(), data.vsaved_peer_id() ? *data.vsaved_peer_id() : MTPPeer(), data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(), @@ -216,6 +217,7 @@ void ScheduledMessages::sendNowSimpleMessage( MTP_flags(flags), update.vid(), peerToMTP(local->from()->id), + MTPint(), // from_boosts_applied peerToMTP(history->peer->id), MTPPeer(), // saved_peer_id MTPMessageFwdHeader(), diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 718421d96..f64717794 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -4449,6 +4449,7 @@ void Session::insertCheckedServiceNotification( MTP_flags(flags), MTP_int(0), // Not used (would've been trimmed to 32 bits). peerToMTP(PeerData::kServiceNotificationsId), + MTPint(), // from_boosts_applied peerToMTP(PeerData::kServiceNotificationsId), MTPPeer(), // saved_peer_id MTPMessageFwdHeader(), diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 7c9910e1e..f7928f006 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1458,6 +1458,10 @@ ServiceAction ParseServiceAction( content.winners = data.vwinners_count().v; content.unclaimed = data.vunclaimed_count().v; result.content = content; + }, [&](const MTPDmessageActionBoostApply &data) { + auto content = ActionBoostApply(); + content.boosts = data.vboosts().v; + result.content = content; }, [](const MTPDmessageActionEmpty &data) {}); return result; } diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 20bd15206..5f2c2da39 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -559,6 +559,10 @@ struct ActionGiveawayResults { int unclaimed = 0; }; +struct ActionBoostApply { + int boosts = 0; +}; + struct ServiceAction { std::variant< v::null_t, @@ -599,7 +603,8 @@ struct ServiceAction { ActionSetChatWallPaper, ActionGiftCode, ActionGiveawayLaunch, - ActionGiveawayResults> content; + ActionGiveawayResults, + ActionBoostApply> content; }; ServiceAction ParseServiceAction( diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index c543de8a5..30397aab6 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -1301,6 +1301,11 @@ auto HtmlWriter::Wrap::pushMessage( return QByteArray::number(data.winners) + " of the giveaway were randomly selected by Telegram " "and received private messages with giftcodes."; + }, [&](const ActionBoostApply &data) { + return serviceFrom + + " boosted the group " + + QByteArray::number(data.boosts) + + (data.boosts > 1 ? " times" : " time"); }, [](v::null_t) { return QByteArray(); }); if (!serviceText.isEmpty()) { diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index dc23fd38f..dde36a5a3 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -617,6 +617,10 @@ QByteArray SerializeMessage( ? "set_same_chat_wallpaper" : "set_chat_wallpaper"); pushReplyToMsgId("message_id"); + }, [&](const ActionBoostApply &data) { + pushActor(); + pushAction("boost_apply"); + push("boosts", data.boosts); }, [](v::null_t) {}); if (v::is_null(message.action.content)) { 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 f9ece93b0..0642a1568 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -116,6 +116,7 @@ MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) { MTP_flags(data.vflags().v & ~removeFlags), data.vid(), data.vfrom_id() ? *data.vfrom_id() : MTPPeer(), + MTPint(), // from_boosts_applied data.vpeer_id(), MTPPeer(), // saved_peer_id data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(), @@ -734,6 +735,7 @@ void GenerateItems( using LogBan = MTPDchannelAdminLogEventActionParticipantToggleBan; using LogPromote = MTPDchannelAdminLogEventActionParticipantToggleAdmin; using LogSticker = MTPDchannelAdminLogEventActionChangeStickerSet; + using LogEmoji = MTPDchannelAdminLogEventActionChangeEmojiStickerSet; using LogPreHistory = MTPDchannelAdminLogEventActionTogglePreHistoryHidden; using LogPermissions = MTPDchannelAdminLogEventActionDefaultBannedRights; @@ -1151,6 +1153,50 @@ void GenerateItems( } }; + const auto createChangeEmojiSet = [&](const LogEmoji &action) { + const auto set = action.vnew_stickerset(); + const auto removed = (set.type() == mtpc_inputStickerSetEmpty); + if (removed) { + const auto text = tr::lng_admin_log_removed_emoji_group( + tr::now, + lt_from, + fromLinkText, + Ui::Text::WithEntities); + addSimpleServiceMessage(text); + } else { + const auto text = tr::lng_admin_log_changed_emoji_group( + tr::now, + lt_from, + fromLinkText, + lt_sticker_set, + Ui::Text::Link( + tr::lng_admin_log_changed_emoji_set(tr::now), + QString()), + Ui::Text::WithEntities); + const auto setLink = std::make_shared([=]( + ClickContext context) { + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + controller->show( + Box( + controller->uiShow(), + Data::FromInputSet(set), + Data::StickersType::Emoji), + Ui::LayerOption::CloseOther); + } + }); + auto message = PreparedServiceText{ text }; + message.links.push_back(fromLink); + message.links.push_back(setLink); + addPart(history->makeMessage( + history->nextNonHistoryEntryId(), + MessageFlag::AdminLogEntry, + date, + std::move(message), + peerToUser(from->id))); + } + }; + const auto createTogglePreHistoryHidden = [&]( const LogPreHistory &action) { const auto hidden = (action.vnew_value().type() == mtpc_boolTrue); @@ -2003,6 +2049,7 @@ void GenerateItems( createParticipantToggleBan, createParticipantToggleAdmin, createChangeStickerSet, + createChangeEmojiSet, createTogglePreHistoryHidden, createDefaultBannedRights, createStopPoll, diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 5da6e880f..6308e01a3 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -4812,6 +4812,20 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return result; }; + auto prepareBoostApply = [&](const MTPDmessageActionBoostApply &action) { + auto result = PreparedServiceText(); + const auto boosts = action.vboosts().v; + result.links.push_back(fromLink()); + result.text = tr::lng_action_boost_apply( + tr::now, + lt_count, + boosts, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + return result; + }; + setServiceText(action.match( prepareChatAddUserText, prepareChatJoinedByLink, @@ -4853,6 +4867,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { prepareGiftCode, prepareGiveawayLaunch, prepareGiveawayResults, + prepareBoostApply, PrepareErrorText)); // Additional information. diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index d22012e17..a1ebf13f6 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -101,7 +101,7 @@ channel#aadfc8f flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5 channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull; -channelFull#f2bcb6f flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper = ChatFull; +channelFull#44c054a7 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -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#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; +message#1e4c8a69 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 from_boosts_applied:flags.29?int 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; @@ -175,6 +175,7 @@ messageActionSetChatWallPaper#5060a3f4 flags:# same:flags.0?true for_both:flags. messageActionGiftCode#678c2e09 flags:# via_giveaway:flags.0?true unclaimed:flags.2?true boost_peer:flags.1?Peer months:int slug:string currency:flags.2?string amount:flags.2?long crypto_currency:flags.3?string crypto_amount:flags.3?long = MessageAction; messageActionGiveawayLaunch#332ba9ed = MessageAction; messageActionGiveawayResults#2a9fadc5 winners_count:int unclaimed_count:int = MessageAction; +messageActionBoostApply#cc02aa6d boosts:int = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -980,6 +981,7 @@ channelAdminLogEventActionChangePeerColor#5796e780 prev_value:PeerColor new_valu channelAdminLogEventActionChangeProfilePeerColor#5e477b25 prev_value:PeerColor new_value:PeerColor = ChannelAdminLogEventAction; channelAdminLogEventActionChangeWallpaper#31bb5d52 prev_value:WallPaper new_value:WallPaper = ChannelAdminLogEventAction; channelAdminLogEventActionChangeEmojiStatus#3ea9feb1 prev_value:EmojiStatus new_value:EmojiStatus = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeEmojiStickerSet#46d840ab prev_stickerset:InputStickerSet new_stickerset:InputStickerSet = ChannelAdminLogEventAction; channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent; @@ -1627,7 +1629,7 @@ peerColor#b54b5acf flags:# color:flags.0?int background_emoji_id:flags.1?long = help.peerColorSet#26219a58 colors:Vector = help.PeerColorSet; help.peerColorProfileSet#767d61eb palette_colors:Vector bg_colors:Vector story_colors:Vector = help.PeerColorSet; -help.peerColorOption#ef8430ab flags:# hidden:flags.0?true color_id:int colors:flags.1?help.PeerColorSet dark_colors:flags.2?help.PeerColorSet channel_min_level:flags.3?int = help.PeerColorOption; +help.peerColorOption#adec6ebe flags:# hidden:flags.0?true color_id:int colors:flags.1?help.PeerColorSet dark_colors:flags.2?help.PeerColorSet channel_min_level:flags.3?int group_min_level:flags.4?int = help.PeerColorOption; help.peerColorsNotModified#2ba1f5ce = help.PeerColors; help.peerColors#f8ed08 hash:int colors:Vector = help.PeerColors; @@ -2111,6 +2113,8 @@ channels.updateColor#d8aa3671 flags:# for_profile:flags.1?true channel:InputChan channels.toggleViewForumAsMessages#9738bb15 channel:InputChannel enabled:Bool = Updates; channels.getChannelRecommendations#83b70d97 channel:InputChannel = messages.Chats; channels.updateEmojiStatus#f0d3e6a8 channel:InputChannel emoji_status:EmojiStatus = Updates; +channels.setBoostsToUnblockRestrictions#ad399cee channel:InputChannel boosts:int = Updates; +channels.setEmojiStickers#3cd930b7 channel:InputChannel stickerset:InputStickerSet = Bool; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -2248,4 +2252,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 173 +// LAYER 174 diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index a8466de5e..6620c4bbb 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -172,6 +172,7 @@ AdminLog::OwnedItem GenerateForwardedItem( MTP_flags(flags), MTP_int(0), // Not used (would've been trimmed to 32 bits). peerToMTP(history->peer->id), + MTPint(), // from_boosts_applied peerToMTP(history->peer->id), MTPPeer(), // saved_peer_id MTP_messageFwdHeader( From 33207b78d5d8a0aee2fa1344234acddba9580004 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 2 Feb 2024 17:08:56 +0400 Subject: [PATCH 22/73] Show applied boosts in message bubbles. --- .../Resources/icons/stories/boosts_mini.png | Bin 0 -> 374 bytes .../icons/stories/boosts_mini@2x.png | Bin 0 -> 690 bytes .../icons/stories/boosts_mini@3x.png | Bin 0 -> 1030 bytes Telegram/SourceFiles/data/data_channel.cpp | 2 + Telegram/SourceFiles/data/data_channel.h | 1 + Telegram/SourceFiles/history/history_item.cpp | 47 ++++++----- Telegram/SourceFiles/history/history_item.h | 14 ++-- .../history/history_item_components.h | 5 ++ .../history/view/history_view_message.cpp | 75 +++++++++++++----- .../history/view/history_view_message.h | 3 +- Telegram/SourceFiles/ui/chat/chat.style | 5 ++ 11 files changed, 108 insertions(+), 44 deletions(-) create mode 100644 Telegram/Resources/icons/stories/boosts_mini.png create mode 100644 Telegram/Resources/icons/stories/boosts_mini@2x.png create mode 100644 Telegram/Resources/icons/stories/boosts_mini@3x.png diff --git a/Telegram/Resources/icons/stories/boosts_mini.png b/Telegram/Resources/icons/stories/boosts_mini.png new file mode 100644 index 0000000000000000000000000000000000000000..80c667ecc925aee7e1e52c961634c54ae3ff22b8 GIT binary patch literal 374 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K$e9(cMqhG>W; zCrBtQ*u8uAwr$f|TT`E%nb|UT&h+Wu|NsC0=+UFd&1rXc7PmLFH8eET|NqBmI%CF+ zKYxA}pPr`s!(_(n+5dljW?v!2v17*$E^hA0lO`2p1%!nB`S_UK#dHFT6i`R)0$XXb zoDEyHv}7kYZ7wM(@tb4u@!ehP4i0JQ+fA(8r%s=)tgJjg$C5d`?c9ys<@!g27W79) zMd`)wJ9GYg|2mGD|9^Z;UeG409kyi25}VpzAz@)+kz(A^FTPAx_kZ>JHS^uHgoFlH s)>#4FVdQ&MBb@024-wjsO4v literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/stories/boosts_mini@2x.png b/Telegram/Resources/icons/stories/boosts_mini@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..95a8063967eb63a0fe98456c6a3f58ede1d23540 GIT binary patch literal 690 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={W7>k44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzSVF5FO4N|zKE$K4@1CxxWi(`lf z@7s{Qr*{TQ)O{9wT0E&SWkSD>8?za6YS1PL9=F9o|9IoOgTx|LQob5WXapL0&g5C5 zasSUctI|u4Yv(^d`TOmcAo$F>(`p|e1G2Zxh%fuw%F0M)GF3$ z?y`6Jy!-Fh@4vtOwyt8(>Z_Aarfk3cciQwT)-rvkpMI*aS$0`dU}eayyzLM9*qZ+< zEV%xfzn%HThE-R!B)D6d3cZ&K?QUqW?O74DQl|fS#>^DHc4vvW_0xB3zS$#@U+1cH zv1q5t*-7hfzYWt65#eNO&JUWr>0*Y<&JIPfZe20%f0>(aGHks4_C0rklH{B1w|g%= zeD1Vxf{W6^6WiNfM2jSt&Hnncre}qN!i(>}mtW?r*co#!#mH~@(@T< z|Kiy`YLa{R@3N4-^1R4WC~Iq!{iVHZ*Is|ser#N1H$PNM)USGL6r=lZsorN(+j$S> zB_z7_AAf9YnsX<8_E|L%E+7$8(wvS8a&Z%ClTq51BDb+S|J(><@ z4BZXq8_bhZy72nzg#4{hCt25Be{GRs$0K9;`>!0+45vx$vsuOcxA>?%Uhzcu*o-DV z-P2EtCf$EqCf?l|w)$!CIql_Pt0j5ZW=VCr_^1h&+8;}2|Ch%8k5MUk*}?>lT`r*X N;_2$=vd$@?2>?r_8nplb literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/stories/boosts_mini@3x.png b/Telegram/Resources/icons/stories/boosts_mini@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..423de820910c74308931d06baead868a87d93743 GIT binary patch literal 1030 zcmV+h1o``kP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFa7jc#R9Fe^SUF2;K@`^bTo5<3 zNTNyM)=5BI326g@pr){}u&}TYW2LRFAebgtDEZXuyzB%W7XU?5DGnkX3|MUvz70@f7S3rXbbar+wFE5vrlt^NC zbaV_24M|ANhc=t-{rx?aN(~MUiel~U?GFzRpP!$3d3hqQEjF9YPft(C2EV4JCKhXK zY`nd_1sMv3nA98;7Z=B3u|LVAzP`R#&uBDWUS1NvwY9~(w!mO81cO0R#ouHyNqVKF zrN_s|%+JowYMTc~KRi5SRe+0&3!0jrpBH(z+s!VS07E85a+~$__14x_k>`XY{OReb zXr>AFdc9%|2v1E-K`b{n7bi=cwOA~gB*8()$HzrI#$8=qu(4XL%mDDlVt186e}6wt zlPJj;Ck|pxr;{0gqobq0&EQw&4tBeJX=w=`MeYJ^bR+O5CnpYv1D&w0u5M&xgg2Jl zC0hoK?)CLmT)l{&pP%Id=nE|^Epq;QMq#;fhsw&ztE($C82J=2NY3SQU0+|*@c#Zj z5C{;isHotgzP!9Brk*aWGG9JRA=5@L#~dzyPl=bFjU=O=7*hz3hell*yL*VGk7*6)i381b!Ac@{dwWB#A_0%b!&B*csS2r3 z2=NWXsvjR8c(9Ve*w`5JJ3Bjtg@vkVD+WSmV?6@6-TM0a$QU~p`v!PS@_5c6?07*qoM6N<$f>9I0 AO8@`> literal 0 HcmV?d00001 diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index fecbe6287..e130dac18 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -1118,6 +1118,8 @@ void ApplyChannelUpdate( if (stickersChanged) { session->changes().peerUpdated(channel, UpdateFlag::StickersSet); } + + channel->mgInfo->boostsApplied = update.vboosts_applied().value_or_empty(); } channel->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty())); channel->setTranslationDisabled(update.is_translations_disabled()); diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index d55e8a4e6..680627e0e 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -132,6 +132,7 @@ public: }; mutable int lastParticipantsStatus = LastParticipantsUpToDate; int lastParticipantsCount = 0; + int boostsApplied = 0; private: ChatData *_migratedFrom = nullptr; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 6308e01a3..006d2f87c 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -139,6 +139,7 @@ struct HistoryItem::CreateConfig { UserId viaBotId = 0; int viewsCount = -1; int forwardsCount = -1; + int boostsApplied = 0; QString postAuthor; MsgId originalId = 0; @@ -352,6 +353,8 @@ HistoryItem::HistoryItem( FlagsFromMTP(id, data.vflags().v, localFlags), data.vdate().v, data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) { + _boostsApplied = data.vfrom_boosts_applied().value_or_empty(); + const auto media = data.vmedia(); const auto checked = media ? CheckMessageMedia(*media) @@ -1474,8 +1477,9 @@ void HistoryItem::returnSavedMedia() { return; } const auto wasGrouped = history()->owner().groups().isGrouped(this); - _media = std::move(_savedLocalEditMediaData->media); - setText(_savedLocalEditMediaData->text); + const auto data = Get(); + _media = std::move(data->media); + setText(data->text); clearSavedMedia(); if (wasGrouped) { history()->owner().groups().refreshMessage(this, true); @@ -1488,19 +1492,18 @@ void HistoryItem::returnSavedMedia() { void HistoryItem::savePreviousMedia() { Expects(_media != nullptr); - using Data = SavedMediaData; - _savedLocalEditMediaData = std::make_unique(Data{ - .text = originalText(), - .media = _media->clone(this), - }); + AddComponents(HistoryMessageSavedMediaData::Bit()); + const auto data = Get(); + data->text = originalText(); + data->media = _media->clone(this); } bool HistoryItem::isEditingMedia() const { - return _savedLocalEditMediaData != nullptr; + return Has(); } void HistoryItem::clearSavedMedia() { - _savedLocalEditMediaData = nullptr; + RemoveComponents(HistoryMessageSavedMediaData::Bit()); } bool HistoryItem::definesReplyKeyboard() const { @@ -1652,9 +1655,10 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { // } //} + const auto editingMedia = isEditingMedia(); const auto updatingSavedLocalEdit = !edition.savePreviousMedia - && (_savedLocalEditMediaData != nullptr); - if (!_savedLocalEditMediaData && edition.savePreviousMedia) { + && editingMedia; + if (!editingMedia && edition.savePreviousMedia) { savePreviousMedia(); } Assert(!updatingSavedLocalEdit || !isLocalUpdateMedia()); @@ -1683,7 +1687,7 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { setReplyMarkup(base::take(edition.replyMarkup)); } if (updatingSavedLocalEdit) { - _savedLocalEditMediaData->media = edition.mtpMedia + Get()->media = edition.mtpMedia ? CreateMedia(this, *edition.mtpMedia) : nullptr; } else { @@ -1700,13 +1704,13 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { setForwardsCount(edition.forwards); } const auto &checkedMedia = updatingSavedLocalEdit - ? _savedLocalEditMediaData->media + ? Get()->media : _media; auto updatedText = checkedMedia ? edition.textWithEntities : EnsureNonEmpty(edition.textWithEntities); if (updatingSavedLocalEdit) { - _savedLocalEditMediaData->text = std::move(updatedText); + Get()->text = std::move(updatedText); } else { setText(std::move(updatedText)); addToSharedMediaIndex(); @@ -1866,7 +1870,7 @@ void HistoryItem::applySentMessage( void HistoryItem::updateSentContent( const TextWithEntities &textWithEntities, const MTPMessageMedia *media) { - if (_savedLocalEditMediaData) { + if (isEditingMedia()) { return; } setText(textWithEntities); @@ -1998,10 +2002,9 @@ void HistoryItem::destroyHistoryEntry() { } Storage::SharedMediaTypesMask HistoryItem::sharedMediaTypes() const { - auto result = Storage::SharedMediaTypesMask {}; - const auto media = _savedLocalEditMediaData - ? _savedLocalEditMediaData->media.get() - : _media.get(); + auto result = Storage::SharedMediaTypesMask{}; + const auto saved = Get(); + const auto media = saved ? saved->media.get() : _media.get(); if (media) { result.set(media->sharedMediaTypes()); } @@ -3403,6 +3406,12 @@ void HistoryItem::createComponents(CreateConfig &&config) { } else { _flags &= ~MessageFlag::HasReplyMarkup; } + + if (out() && isSending()) { + if (const auto channel = _history->peer->asMegagroup()) { + _boostsApplied = channel->mgInfo->boostsApplied; + } + } } bool HistoryItem::checkRepliesPts( diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 3c5df4f25..0525e5c12 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -21,6 +21,7 @@ struct HistoryMessageMarkupData; struct HistoryMessageReplyMarkup; struct HistoryMessageTranslation; struct HistoryMessageForwarded; +struct HistoryMessageSavedMediaData; struct HistoryServiceDependentData; enum class HistorySelfDestructType; struct PreparedServiceText; @@ -536,16 +537,15 @@ public: return _ttlDestroyAt; } + [[nodiscard]] int boostsApplied() const { + return _boostsApplied; + } + MsgId id; private: struct CreateConfig; - struct SavedMediaData { - TextWithEntities text; - std::unique_ptr media; - }; - HistoryItem( not_null history, MsgId id, @@ -655,13 +655,13 @@ private: TextWithEntities _text; - std::unique_ptr _savedLocalEditMediaData; std::unique_ptr _media; std::unique_ptr _reactions; crl::time _reactionsLastRefreshed = 0; TimeId _date = 0; TimeId _ttlDestroyAt = 0; + int _boostsApplied = 0; HistoryView::Element *_mainView = nullptr; MessageGroupId _groupId = MessageGroupId(); @@ -672,3 +672,5 @@ private: friend class HistoryView::ServiceMessagePainter; }; + +constexpr auto kSize = int(sizeof(HistoryItem)); diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index bd0eb6a9d..4d60ff9a3 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -149,6 +149,11 @@ struct HistoryMessageForwarded : public RuntimeComponent { + TextWithEntities text; + std::unique_ptr media; +}; + struct HistoryMessageSaved : public RuntimeComponent { Data::SavedSublist *sublist = nullptr; }; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 3db8124cb..12ac5e714 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -454,19 +454,20 @@ void Message::setReactions(std::unique_ptr list) { } void Message::refreshRightBadge() { + const auto item = data(); const auto text = [&] { - if (data()->isDiscussionPost()) { + if (item->isDiscussionPost()) { return (delegate()->elementContext() == Context::Replies) ? QString() : tr::lng_channel_badge(tr::now); - } else if (data()->author()->isMegagroup()) { - if (const auto msgsigned = data()->Get()) { + } else if (item->author()->isMegagroup()) { + if (const auto msgsigned = item->Get()) { Assert(msgsigned->isAnonymousRank); return msgsigned->postAuthor; } } - const auto channel = data()->history()->peer->asMegagroup(); - const auto user = data()->author()->asUser(); + const auto channel = item->history()->peer->asMegagroup(); + const auto user = item->author()->asUser(); if (!channel || !user) { return QString(); } @@ -485,13 +486,41 @@ void Message::refreshRightBadge() { ? tr::lng_admin_badge(tr::now) : QString(); }(); - const auto badge = text.isEmpty() - ? delegate()->elementAuthorRank(this) - : TextUtilities::RemoveEmoji(TextUtilities::SingleLine(text)); - if (badge.isEmpty()) { + auto badge = TextWithEntities{ + (text.isEmpty() + ? delegate()->elementAuthorRank(this) + : TextUtilities::RemoveEmoji(TextUtilities::SingleLine(text))) + }; + _rightBadgeHasBoosts = 0; + if (const auto boosts = item->boostsApplied()) { + _rightBadgeHasBoosts = 1; + + const auto many = (boosts > 1); + const auto &icon = many + ? st::boostsMessageIcon + : st::boostMessageIcon; + const auto padding = many + ? st::boostsMessageIconPadding + : st::boostMessageIconPadding; + const auto owner = &item->history()->owner(); + auto added = Ui::Text::SingleCustomEmoji( + owner->customEmojiManager().registerInternalEmoji(icon, padding) + ).append(many ? QString::number(boosts) : QString()); + badge.append(' ').append(Ui::Text::Colorized(added, 1)); + } + if (badge.empty()) { _rightBadge.clear(); } else { - _rightBadge.setText(st::defaultTextStyle, badge); + const auto context = Core::MarkedTextContext{ + .session = &item->history()->session(), + .customEmojiRepaint = [] {}, + .customEmojiLoopLimit = 1, + }; + _rightBadge.setMarkedText( + st::defaultTextStyle, + badge, + Ui::NameTextOptions(), + context); } } @@ -1482,20 +1511,30 @@ void Message::paintFromName( } if (rightWidth) { p.setPen(stm->msgDateFg); - p.setFont(ClickHandler::showAsActive(_fastReplyLink) - ? st::msgFont->underline() - : st::msgFont); if (replyWidth) { + p.setFont(ClickHandler::showAsActive(_fastReplyLink) + ? st::msgFont->underline() + : st::msgFont); p.drawText( trect.left() + trect.width() - rightWidth, trect.top() + st::msgFont->ascent, FastReplyText()); } else { - _rightBadge.draw( - p, - trect.left() + trect.width() - rightWidth, - trect.top(), - rightWidth); + const auto shift = QPoint(trect.width() - rightWidth, 0); + const auto pen = !_rightBadgeHasBoosts + ? QPen() + : !context.outbg + ? QPen(FromNameFg(context, colorIndex())) + : stm->msgServiceFg->p; + auto colored = std::array{ + { { &pen, &pen } }, + }; + _rightBadge.draw(p, { + .position = trect.topLeft() + shift, + .availableWidth = rightWidth, + .colors = colored, + .now = context.now, + }); } } trect.setY(trect.y() + st::msgNameFont->height); diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 1f13056e8..62ab6c02b 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -305,9 +305,10 @@ private: mutable std::unique_ptr _fromNameStatus; Ui::Text::String _rightBadge; mutable int _fromNameVersion = 0; - uint32 _bubbleWidthLimit : 30 = 0; + uint32 _bubbleWidthLimit : 29 = 0; uint32 _invertMedia : 1 = 0; uint32 _hideReply : 1 = 0; + uint32 _rightBadgeHasBoosts : 1 = 0; BottomInfo _bottomInfo; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 6ec3aeb04..2a2733f2a 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1042,3 +1042,8 @@ chatSimilarSkip: 12px; premiumRequiredWidth: 186px; premiumRequiredIcon: icon{{ "chat/large_lockedchat", msgServiceFg }}; premiumRequiredCircle: 60px; + +boostMessageIcon: icon {{ "stories/boost_mini", windowFg }}; +boostMessageIconPadding: margins(0px, 2px, 0px, 0px); +boostsMessageIcon: icon {{ "stories/boosts_mini", windowFg }}; +boostsMessageIconPadding: margins(0px, 2px, 0px, 0px); From cb174cb0bf000746c60040b7f71ae1cb8dc2e954 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 2 Feb 2024 17:37:41 +0400 Subject: [PATCH 23/73] Add some more premium promo sections. --- .../icons/settings/premium/lastseen.png | Bin 0 -> 553 bytes .../icons/settings/premium/lastseen@2x.png | Bin 0 -> 1015 bytes .../icons/settings/premium/lastseen@3x.png | Bin 0 -> 1455 bytes .../icons/settings/premium/privacy.png | Bin 0 -> 597 bytes .../icons/settings/premium/privacy@2x.png | Bin 0 -> 1108 bytes .../icons/settings/premium/privacy@3x.png | Bin 0 -> 1611 bytes Telegram/Resources/langs/lang.strings | 4 ++ .../SourceFiles/boxes/premium_preview_box.cpp | 10 ++++ .../SourceFiles/boxes/premium_preview_box.h | 2 + Telegram/SourceFiles/settings/settings.style | 2 + .../SourceFiles/settings/settings_premium.cpp | 46 ++++++++++++++---- 11 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 Telegram/Resources/icons/settings/premium/lastseen.png create mode 100644 Telegram/Resources/icons/settings/premium/lastseen@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/lastseen@3x.png create mode 100644 Telegram/Resources/icons/settings/premium/privacy.png create mode 100644 Telegram/Resources/icons/settings/premium/privacy@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/privacy@3x.png diff --git a/Telegram/Resources/icons/settings/premium/lastseen.png b/Telegram/Resources/icons/settings/premium/lastseen.png new file mode 100644 index 0000000000000000000000000000000000000000..9299ed21638a2e254215c442c075c1f2b07d409f GIT binary patch literal 553 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 E0K)x?AV~B;| z+Ys;7j)@|r9#29~UGbU1S=Y#Oc1nH1A_PvxXWh!HS-R6t3)shg{%IrCyYJhk+i#l>Hi$j{yfZ*!$>o=2yU(gH80qhs zb#_^dK=)A{F>eEjD_L8&M)h9#TE*Lb_@WE_85^zmoSfx7+mX0uO5PCwl`_mr_v+}g03=MM`6%=;JL%rRShu|qMS zw#7$HxwA9CUA)0{!j(t<)B6_xIeJ&~~` zLdR?Ar0cIkIo>rEu*dty1paAQbv0}CRVS{+FG{QoC(rnFGNnm-*{c8i^%olXKX0%5 UIcblGIw(p#UHx3vIVCg!08xGRlK=n! literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/privacy@2x.png b/Telegram/Resources/icons/settings/premium/privacy@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f82d78947fa763eeebc6ee84aa704e95f81fdce1 GIT binary patch literal 1108 zcmV-a1grarP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFzDYzuR9Fe^SW75%Q55!mQ}X5~ z1CNxtMG6C@Bm<@7Q4%Gkl$0`&k)e@^x&vZj;1P;a=nk$ZMTr<75(yD`zyJT!soi$g z-skLdyOY1p9j*QC^?hsIb=KZz-Rt9Hf9wj_6|gJdK?MQ=0^;N2gMxxQ;OZ#8y0zjm zGBUEPtgND1l_<;dwA)W^{D)$jHd&=cidFzxKbszmZl-N{X?#<=ujUg5%@kU!kll zuCA`i%gZfCl@aRe>)+nq3=%-u_V)Jf?(PhH@B}+MJ7pm(;Amchj(K=^Xl-o`4-bbT z`WhXxwzeiq3y-g{vC%?o>YAIIdw+i~3xI60v$J(9e}DhU$w?Un->tN?R7X)=;v287 zuF9l=vqw-{TRSy1)!*MA6%_^bxVX6M>uVYD;NT!QI9N3a-mj^tNhS&$^^b^%fPZy$ zHFI;hT$;kr&=3Qu(bLnz2U4<8o|BUks>P7~{e4Xcr<#E8Yx)OPXG;N21u68K$yi`gh`Wo0E>@`f}a9ChB$>vmmToi)v^ ztu0-h`Q`HRa$#Yirid2)!+in+1A!+cB~46B@D!bmuD6yD-ita3w%pv@#MxGno12?s zENX;?hN_7C_`sF&8Pd_wp(s>URmE7;$jr>7I^rG*3k##J48efGY-58pzR*n=MVt_x z7hYaM6)%EkXJ=GRoDlrZ&(Ep17m?T3SK;_3O$evVi{Yu%?(QxHfk+dYnVAt1yanRj zCzO&VgcJMm@gd}R1;i~S)R88HY0K$!3OOMmAwryjvYTdpe*Wm_i29o#v9YnjAyAem zmFw;8rPmMI8NU>$vl7zQ)y}gZ5 z3sVu@!swEoo}QPN=jW$yiP_oNnwlES*OaA-;8*F+&JO*e9v~|#E9(7clpo#|9)#CX z#>U393DYQ&%3e-Rj!xR@^7;80y{HmP$)~%!+iC{P|K;UnU|;~Vky0cTCrrMlr>86c z47b~zpP%2{+>ERI>FJ5zVm1!;($Z2_R~P<=P>I6(n4GfV;o+8+7L9;Za088xkF%47 zdoL?1D>*qCHNf16OAGS`8V-j5x$bkb4T>-lSb_M?1 a3j6~o4iorkU}C5M0000Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS=wMj%lRA>e5T3IM(Z5X%Ckk1w) z%TT1jhcuF%8_CFWW4ReFTu9B8E1@ViiYTO+3u8&h79~p)5)vg#Da?=|`%-qYjqlg@ zuQTWG{QvL!p5s_9&bfNdvp>&ym*;(-qobodN(Ph+C>c;PpkzSFfRX_v1CPvr?jz+H z#m&sj{`vp8y}dmH#hK}@i{y^Y;A2#Nl7s@G<YH{ajL@8)qSdI!ZavCLqj(gp~oE*LfZ`SS;|ayd`Z^X=O=MxVeoH8thw=_yp_ zM#09$5eCTAE6 zcUxN<_Bsrz{{DVvXJ?KS6BEO(GdDMv6O;|e$jDJktrKQTYfq}uy%nV<4dwbi$ z!a_yGwt>2lPbI)8px?iLFIz}Z1TPN`4hSY6kR~T5IrE=Ce||v4Ye7K)$I#f=C}hxv zX>M-j1POp1fyO7nIV&qGK86f$ZfZQ(*Y|FI;P@ z=Q1QD#KFNq9Vh9nx3^c@R2Jf~(KR$Q1O^6jrr^ZCg2%+k6^0&|si~lY3D(={;4C0yQ=2RB~*%auRhQP{r%(>$|wPNY?!Q z`*&J${?bJ^*FJpsFflPfJ~d!|qg#tgpQW>mp8Lm-AH&1LasBw$F9C#&3*(%+$_wPg zAR;0np0q6{{BE;BKi;Kw &media) { return tr::lng_premium_summary_subtitle_infinite_reactions(); case PremiumPreview::TagsForMessages: return tr::lng_premium_summary_subtitle_tags_for_messages(); + case PremiumPreview::LastSeen: + return tr::lng_premium_summary_subtitle_last_seen(); + case PremiumPreview::MessagePrivacy: + return tr::lng_premium_summary_subtitle_message_privacy(); case PremiumPreview::Stickers: return tr::lng_premium_summary_subtitle_premium_stickers(); case PremiumPreview::AnimatedEmoji: @@ -150,6 +154,10 @@ void PreloadSticker(const std::shared_ptr &media) { return tr::lng_premium_summary_about_infinite_reactions(); case PremiumPreview::TagsForMessages: return tr::lng_premium_summary_about_tags_for_messages(); + case PremiumPreview::LastSeen: + return tr::lng_premium_summary_about_last_seen(); + case PremiumPreview::MessagePrivacy: + return tr::lng_premium_summary_about_message_privacy(); case PremiumPreview::Stickers: return tr::lng_premium_summary_about_premium_stickers(); case PremiumPreview::AnimatedEmoji: @@ -480,6 +488,8 @@ struct VideoPreviewDocument { case PremiumPreview::AnimatedUserpics: return "animated_userpics"; case PremiumPreview::RealTimeTranslation: return "translations"; case PremiumPreview::Wallpapers: return "wallpapers"; + case PremiumPreview::LastSeen: return "last_seen"; + case PremiumPreview::MessagePrivacy: return "message_privacy"; } return ""; }(); diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.h b/Telegram/SourceFiles/boxes/premium_preview_box.h index ba6dae46b..b7fb7a40e 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.h +++ b/Telegram/SourceFiles/boxes/premium_preview_box.h @@ -62,6 +62,8 @@ enum class PremiumPreview { RealTimeTranslation, Wallpapers, TagsForMessages, + LastSeen, + MessagePrivacy, kCount, }; diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index d61f61738..0df986eb0 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -92,6 +92,8 @@ settingsPremiumIconVoice: icon {{ "settings/premium/voice", settingsIconFg }}; settingsPremiumIconFiles: icon {{ "settings/premium/files", settingsIconFg }}; settingsPremiumIconTranslations: icon {{ "settings/premium/translations", settingsIconFg }}; settingsPremiumIconTags: icon {{ "settings/premium/tags", settingsIconFg }}; +settingsPremiumIconLastSeen: icon {{ "settings/premium/lastseen", settingsIconFg }}; +settingsPremiumIconPrivacy: icon {{ "settings/premium/privacy", settingsIconFg }}; settingsStoriesIconOrder: icon {{ "settings/premium/stories_order", premiumButtonBg1 }}; settingsStoriesIconStealth: icon {{ "menu/stealth", premiumButtonBg1 }}; diff --git a/Telegram/SourceFiles/settings/settings_premium.cpp b/Telegram/SourceFiles/settings/settings_premium.cpp index 337f413ac..cde74eb5a 100644 --- a/Telegram/SourceFiles/settings/settings_premium.cpp +++ b/Telegram/SourceFiles/settings/settings_premium.cpp @@ -177,22 +177,26 @@ using Order = std::vector; [[nodiscard]] Order FallbackOrder() { return Order{ - u"wallpapers"_q, u"stories"_q, - u"double_limits"_q, u"more_upload"_q, - u"faster_download"_q, + u"double_limits"_q, + u"last_seen"_q, u"voice_to_text"_q, - u"no_ads"_q, + u"faster_download"_q, + u"translations"_q, + u"animated_emoji"_q, u"emoji_status"_q, u"saved_tags"_q, - u"infinite_reactions"_q, - u"premium_stickers"_q, - u"animated_emoji"_q, - u"advanced_chat_management"_q, + //u"peer_colors"_q, + u"wallpapers"_q, u"profile_badge"_q, + u"message_privacy"_q, + u"advanced_chat_management"_q, + u"no_ads"_q, + //u"app_icons"_q, + u"infinite_reactions"_q, u"animated_userpics"_q, - u"translations"_q, + u"premium_stickers"_q, }; } @@ -208,6 +212,26 @@ using Order = std::vector; true, }, }, + { + u"last_seen"_q, + Entry{ + &st::settingsPremiumIconLastSeen, + tr::lng_premium_summary_subtitle_last_seen(), + tr::lng_premium_summary_about_last_seen(), + PremiumPreview::LastSeen, + true, + }, + }, + { + u"message_privacy"_q, + Entry{ + &st::settingsPremiumIconPrivacy, + tr::lng_premium_summary_subtitle_message_privacy(), + tr::lng_premium_summary_about_message_privacy(), + PremiumPreview::MessagePrivacy, + true, + }, + }, { u"wallpapers"_q, Entry{ @@ -1522,6 +1546,10 @@ not_null CreateSubscribeButton( return PremiumPreview::InfiniteReactions; } else if (s == u"saved_tags"_q) { return PremiumPreview::TagsForMessages; + } else if (s == u"last_seen"_q) { + return PremiumPreview::LastSeen; + } else if (s == u"message_privacy"_q) { + return PremiumPreview::MessagePrivacy; } else if (s == u"premium_stickers"_q) { return PremiumPreview::Stickers; } else if (s == u"animated_emoji"_q) { From f6a8c1e9963d1c5d891c1b4b84fd431c47cd20d9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 2 Feb 2024 21:30:53 +0400 Subject: [PATCH 24/73] Allow boosts / giveaways in groups. --- Telegram/Resources/langs/lang.strings | 70 +++++++++++++--- Telegram/SourceFiles/api/api_statistics.cpp | 3 +- .../SourceFiles/boxes/gift_premium_box.cpp | 83 ++++++++++++------- .../boxes/peers/replace_boost_box.cpp | 28 ++++--- Telegram/SourceFiles/data/data_boosts.h | 1 + Telegram/SourceFiles/history/history_item.cpp | 12 +-- .../view/media/history_view_giveaway.cpp | 25 ++++-- .../info/boosts/create_giveaway_box.cpp | 66 ++++++++++----- .../giveaway/giveaway_list_controllers.cpp | 26 +++--- .../boosts/giveaway/giveaway_type_row.cpp | 11 ++- .../info/boosts/giveaway/giveaway_type_row.h | 3 +- .../info/boosts/info_boosts_inner_widget.cpp | 16 +++- Telegram/SourceFiles/ui/boxes/boost_box.cpp | 77 +++++++++++------ Telegram/SourceFiles/ui/boxes/boost_box.h | 10 ++- .../SourceFiles/window/window_peer_menu.cpp | 7 +- .../window/window_session_controller.cpp | 12 ++- 16 files changed, 302 insertions(+), 148 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 25b35e849..b8fde1519 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1736,6 +1736,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_story_mention_button" = "View Story"; "lng_action_story_mention_me_unavailable" = "The story where you mentioned {user} is no longer available."; "lng_action_story_mention_unavailable" = "The story where {user} mentioned you is no longer available."; +"lng_action_giveaway_started_group" = "{from} just started a giveaway of Telegram Premium subscriptions to its members."; "lng_action_giveaway_started" = "{from} just started a giveaway of Telegram Premium subscriptions to its followers."; "lng_action_giveaway_results#one" = "{count} winner of the giveaway was randomly selected by Telegram and received private messages with giftcodes."; "lng_action_giveaway_results#other" = "{count} winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes."; @@ -2150,13 +2151,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_gifts_terms_policy" = "Privacy Policy"; "lng_boost_channel_button" = "Boost Channel"; +"lng_boost_group_button" = "Boost Group"; "lng_boost_again_button" = "Boost Again"; "lng_boost_level#one" = "Level {count}"; "lng_boost_level#other" = "Level {count}"; "lng_boost_channel_title_first" = "Enable stories for channel"; +"lng_boost_channel_title_first_group" = "Enable stories for group"; "lng_boost_channel_needs_first#one" = "{channel} needs **{count}** more boost to enable posting stories. Help make it possible!"; "lng_boost_channel_needs_first#other" = "{channel} needs **{count}** more boosts to enable posting stories. Help make it possible!"; "lng_boost_channel_title_more" = "Help upgrade channel"; +"lng_boost_channel_title_more_group" = "Help upgrade group"; "lng_boost_channel_needs_more#one" = "{channel} needs **{count}** more boost to be able to {post}."; "lng_boost_channel_needs_more#other" = "{channel} needs **{count}** more boosts to be able to {post}."; "lng_boost_channel_title_max" = "Maximum level reached"; @@ -2168,10 +2172,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boost_channel_reached_first" = "This channel reached **Level 1** and can now post stories."; "lng_boost_channel_reached_more#one" = "This channel reached **Level {count}** and can now {post}."; "lng_boost_channel_reached_more#other" = "This channel reached **Level {count}** and can now {post}."; +"lng_boost_channel_you_first_group#one" = "This group needs **{count}** more boost\nto enable stories."; +"lng_boost_channel_you_first_group#other" = "This group needs **{count}** more boosts\nto enable stories."; +"lng_boost_channel_you_more_group#one" = "This group needs **{count}** more boost\nto be able to {post}."; +"lng_boost_channel_you_more_group#other" = "This group needs **{count}** more boosts\nto be able to {post}."; +"lng_boost_channel_reached_first_group" = "This group reached **Level 1** and can now post stories."; +"lng_boost_channel_reached_more_group#one" = "This group reached **Level {count}** and can now {post}."; +"lng_boost_channel_reached_more_group#other" = "This group reached **Level {count}** and can now {post}."; "lng_boost_channel_post_stories#one" = "post **{count} story** per day"; "lng_boost_channel_post_stories#other" = "post **{count} stories** per day"; "lng_boost_error_gifted_title" = "Can't boost with gifted Premium!"; "lng_boost_error_gifted_text" = "Because your **Telegram Premium** subscription was gifted to you, you can't use it to boost channels."; +"lng_boost_error_gifted_text_group" = "Because your **Telegram Premium** subscription was gifted to you, you can't use it to boost groups."; "lng_boost_need_more" = "More boosts needed"; "lng_boost_need_more_text#one" = "To boost {channel}, gift **Telegram Premium** to a friend and get **{count}** boosts."; "lng_boost_need_more_text#other" = "To boost {channel}, gift **Telegram Premium** to a friend and get **{count}** boosts."; @@ -2179,10 +2191,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boost_need_more_again#other" = "To boost {channel} again, gift **Telegram Premium** to a friend and get **{count}** additional boosts."; "lng_boost_error_already_title" = "Already Boosted!"; "lng_boost_error_already_text" = "You are already boosting this channel."; +"lng_boost_error_already_text_group" = "You are already boosting this group."; "lng_boost_error_premium_title" = "Premium needed!"; +"lng_boost_error_premium_text_group" = "Only **Telegram Premium** subscribers can boost groups. Do you want to subscribe to **Telegram Premium**?"; "lng_boost_error_premium_text" = "Only **Telegram Premium** subscribers can boost channels. Do you want to subscribe to **Telegram Premium**?"; "lng_boost_error_premium_yes" = "Yes"; "lng_boost_error_flood_title" = "Can't boost too often!"; +"lng_boost_error_flood_text_group" = "You can change the group you boost only once a day. Next time you can boost is in {left}."; "lng_boost_error_flood_text" = "You can change the channel you boost only once a day. Next time you can boost is in {left}."; "lng_boost_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?"; "lng_boost_now_replace" = "Replace"; @@ -2224,6 +2239,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_giveaway_new_title" = "Boosts via Gifts"; "lng_giveaway_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers."; +"lng_giveaway_new_about_group" = "Get more boosts for your group by gifting Premium to your subscribers."; "lng_giveaway_create_option" = "Create Giveaway"; "lng_giveaway_create_subtitle" = "winners are chosen randomly"; "lng_giveaway_award_option" = "Award Specific Users"; @@ -2237,23 +2253,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_giveaway_channels_title" = "Channels included in the giveaway"; "lng_giveaway_channels_this#one" = "this channel will receive {count} boost"; "lng_giveaway_channels_this#other" = "this channel will receive {count} boosts"; +"lng_giveaway_channels_this_group#one" = "this group will receive {count} boost"; +"lng_giveaway_channels_this_group#other" = "this group will receive {count} boosts"; "lng_giveaway_channels_add" = "Add Channel"; "lng_giveaway_channels_about" = "Choose the channels the users need to join to take part in the giveaway."; "lng_giveaway_users_title" = "Users eligible for the giveaway"; "lng_giveaway_users_all" = "All subscribers"; +"lng_giveaway_users_all_group" = "All members"; "lng_giveaway_users_from_all_countries" = "from all countries"; "lng_giveaway_users_from_one_country" = "from {country}"; "lng_giveaway_users_from_countries#one" = "from {count} country"; "lng_giveaway_users_from_countries#other" = "from {count} countries"; "lng_giveaway_users_new" = "Only new subscribers"; +"lng_giveaway_users_new_group" = "Only new members"; "lng_giveaway_users_about" = "Choose if you want to limit the giveaway only to those who joined the channel after the giveaway started or to users from specific countries."; +"lng_giveaway_users_about_group" = "Choose if you want to limit the giveaway only to those who joined the group after the giveaway started or to members from specific countries."; "lng_giveaway_start" = "Start Giveaway"; "lng_giveaway_award" = "Gift Premium"; "lng_giveaway_start_sure" = "Are you sure you want to start this prepaid giveaway now? This action cannot be undone."; "lng_giveaway_date_title" = "Date when giveaway ends"; "lng_giveaway_date" = "Date and Time"; -"lng_giveaway_date_about#one" = "Choose when {count} subscriber of your channel will be randomly selected to receive Telegram Premium."; +"lng_giveaway_date_about#one" = "Choose when {count} subscriber of your channel will be randomly selected to receive Telegram Premium."; "lng_giveaway_date_about#other" = "Choose when {count} subscribers of your channel will be randomly selected to receive Telegram Premium."; +"lng_giveaway_date_about_group#one" = "Choose when {count} members of your group will be randomly selected to receive Telegram Premium."; +"lng_giveaway_date_about_group#other" = "Choose when {count} members of your group will be randomly selected to receive Telegram Premium."; "lng_giveaway_duration_title#one" = "Duration of Premium subscription"; "lng_giveaway_duration_title#other" = "Duration of Premium subscriptions"; "lng_giveaway_duration_price" = "{price} x {amount}"; @@ -2283,8 +2306,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_giveaway_created_title" = "Giveaway created"; "lng_giveaway_created_body" = "Check your channels' {link} to see how this giveaway boosted your channel."; +"lng_giveaway_created_body_group" = "Check your groups' {link} to see how this giveaway boosted your group."; "lng_giveaway_awarded_title" = "Premium subscriptions gifted"; "lng_giveaway_awarded_body" = "Check your channels' {link} to see how gifts boosted your channel."; +"lng_giveaway_awarded_body_group" = "Check your groups' {link} to see how gifts boosted your group."; "lng_giveaway_created_link" = "Statistics"; "lng_prize_title" = "Congratulations!"; @@ -2307,8 +2332,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prizes_participants" = "Participants"; "lng_prizes_participants_all#one" = "All subscribers of the channel:"; "lng_prizes_participants_all#other" = "All subscribers of the channels:"; +"lng_prizes_participants_all_group#one" = "All members of the group:"; +"lng_prizes_participants_all_group#other" = "All members of the groups:"; +"lng_prizes_participants_all_mixed#one" = "All members of the group:"; +"lng_prizes_participants_all_mixed#other" = "All members of the groups and channels:"; "lng_prizes_participants_new#one" = "All users who joined the channel below after this date:"; "lng_prizes_participants_new#other" = "All users who joined the channels below after this date:"; +"lng_prizes_participants_new_group#one" = "All users who joined the group below after this date:"; +"lng_prizes_participants_new_group#other" = "All users who joined the groups below after this date:"; +"lng_prizes_participants_new_mixed#one" = "All users who joined the group below after this date:"; +"lng_prizes_participants_new_mixed#other" = "All users who joined the groups and channels below after this date:"; "lng_prizes_countries" = "from {countries}"; "lng_prizes_countries_and_one" = "{countries}, {country}"; "lng_prizes_countries_and_last" = "{countries} and {country}"; @@ -2319,32 +2352,43 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prizes_how_text" = "This giveaway is sponsored by {admins}."; "lng_prizes_end_text" = "This giveaway was sponsored by {admins}."; "lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its followers"; -"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers."; +"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers"; +"lng_prizes_admins_group#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its members"; +"lng_prizes_admins_group#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its members"; "lng_prizes_additional_added#one" = "{channel} also included **{count} {prize}** in the prize. Admins of the channel are responsible for delivering this prize."; "lng_prizes_additional_added#other" = "{channel} also included **{count} {prize}** in the prizes. Admins of the channel are responsible for delivering these prizes."; +"lng_prizes_additional_added_group#one" = "{channel} also included **{count} {prize}** in the prize. Admins of the group are responsible for delivering this prize."; +"lng_prizes_additional_added_group#other" = "{channel} also included **{count} {prize}** in the prizes. Admins of the group are responsible for delivering these prizes."; "lng_prizes_how_when_finish" = "On {date}, Telegram will automatically select {winners}."; "lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}."; "lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link."; "lng_prizes_end_activated#other" = "**{count}** of the winners already used their gift links."; -"lng_prizes_winners_all_of_one#one" = "{count} random subscribers of {channel}"; +"lng_prizes_winners_all_of_one#one" = "{count} random subscriber of {channel}"; "lng_prizes_winners_all_of_one#other" = "{count} random subscribers of {channel}"; -"lng_prizes_winners_all_of_many#one" = "{count} random subscribers of {channel} and other listed channels"; -"lng_prizes_winners_all_of_many#other" = "{count} random subscribers of {channel} and other listed channels"; +"lng_prizes_winners_all_of_one_group#one" = "{count} random member of {channel}"; +"lng_prizes_winners_all_of_one_group#other" = "{count} random members of {channel}"; +"lng_prizes_winners_all_of_many#one" = "{count} random subscriber of {channel} and other listed groups and channels"; +"lng_prizes_winners_all_of_many#other" = "{count} random subscribers of {channel} and other listed groups and channels"; +"lng_prizes_winners_all_of_many_group#one" = "{count} random member of {channel} and other listed groups and channels"; +"lng_prizes_winners_all_of_many_group#other" = "{count} random members of {channel} and other listed groups and channels"; "lng_prizes_winners_new_of_one#one" = "{count} random user that joined {channel} after {start_date}"; "lng_prizes_winners_new_of_one#other" = "{count} random users that joined {channel} after {start_date}"; -"lng_prizes_winners_new_of_many#one" = "{count} random user that joined {channel} and other listed channels after {start_date}"; -"lng_prizes_winners_new_of_many#other" = "{count} random users that joined {channel} and other listed channels after {start_date}"; -"lng_prizes_how_participate_one" = "To take part in this giveaway please join channel {channel} before {date}."; -"lng_prizes_how_participate_many" = "To take part in this giveaway please join channel {channel} and other listed channels before {date}."; +"lng_prizes_winners_new_of_many#one" = "{count} random user that joined {channel} and other listed groups and channels after {start_date}"; +"lng_prizes_winners_new_of_many#other" = "{count} random users that joined {channel} and other listed groups and channels after {start_date}"; +"lng_prizes_how_participate_one" = "To take part in this giveaway please join {channel} before {date}."; +"lng_prizes_how_participate_many" = "To take part in this giveaway please join {channel} and other listed groups and channels before {date}."; "lng_prizes_how_no_admin" = "You are not eligible to participate in this giveaway, because you are an admin of participating channel ({channel})."; +"lng_prizes_how_no_admin_group" = "You are not eligible to participate in this giveaway, because you are an admin of participating group ({channel})."; "lng_prizes_how_no_joined" = "You are not eligible to participate in this giveaway, because you joined this channel on {date}, which is before the contest started."; +"lng_prizes_how_no_joined_group" = "You are not eligible to participate in this giveaway, because you joined this group on {date}, which is before the contest started."; "lng_prizes_how_no_country" = "You are not eligible to participate in this giveaway, because your country is not included in the terms of the giveaway."; -"lng_prizes_how_yes_joined_one" = "You are participating in this giveaway, because you have joined channel {channel}."; -"lng_prizes_how_yes_joined_many" = "You are participating in this giveaway, because you have joined channel {channel} (and other listed channels)."; +"lng_prizes_how_yes_joined_one" = "You are participating in this giveaway, because you have joined {channel}."; +"lng_prizes_how_yes_joined_many" = "You are participating in this giveaway, because you have joined {channel} (and other listed groups and channels)."; "lng_prizes_you_won" = "You won a prize in this giveaway {cup}"; "lng_prizes_view_prize" = "View my prize"; "lng_prizes_you_didnt" = "You didn't win a prize in this giveaway."; "lng_prizes_cancelled" = "The channel cancelled the prizes by reversing the payment for them."; +"lng_prizes_cancelled_group" = "The channel cancelled the prizes by reversing the payment for them."; "lng_prizes_badge" = "x{amount}"; "lng_prizes_results_title" = "Winners Selected!"; @@ -4592,10 +4636,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boosts_level" = "Level"; "lng_boosts_existing" = "Existing boosts"; "lng_boosts_premium_audience" = "Premium subscribers"; +"lng_boosts_premium_members" = "Premium members"; "lng_boosts_next_level" = "Boosts to level up"; "lng_boosts_list_title#one" = "{count} Boost"; "lng_boosts_list_title#other" = "{count} Boosts"; "lng_boosts_list_subtext" = "Your channel is currently boosted by these users."; +"lng_boosts_list_subtext_group" = "Your group is currently boosted by these users."; "lng_boosts_show_more_boosts#one" = "Show {count} More Boosts"; "lng_boosts_show_more_boosts#other" = "Show {count} More Boosts"; "lng_boosts_show_more_gifts#one" = "Show {count} More Boosts"; @@ -4603,8 +4649,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boosts_list_status" = "boost expires on {date}"; "lng_boosts_link_title" = "Link for boosting"; "lng_boosts_link_subtext" = "Share this link with your subscribers to get more boosts."; +"lng_boosts_link_subtext_group" = "Share this link with the members of your group to get more boosts."; "lng_boosts_get_boosts" = "Get Boosts via Gifts"; "lng_boosts_get_boosts_subtext" = "Get more boosts for your channel by gifting Telegram Premium to your subscribers."; +"lng_boosts_get_boosts_subtext_group" = "Get more boosts for your group by gifting Telegram Premium to the members."; "lng_boosts_list_unclaimed" = "Unclaimed"; "lng_boosts_list_pending" = "To be distributed"; "lng_boosts_list_pending_about" = "The recipient will be selected when the giveaway ends."; diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp index f24e156d0..789211f33 100644 --- a/Telegram/SourceFiles/api/api_statistics.cpp +++ b/Telegram/SourceFiles/api/api_statistics.cpp @@ -604,7 +604,7 @@ rpl::producer Boosts::request() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); const auto channel = _peer->asChannel(); - if (!channel || channel->isMegagroup()) { + if (!channel) { return lifetime; } @@ -628,6 +628,7 @@ rpl::producer Boosts::request() { const auto slots = data.vmy_boost_slots(); _boostStatus.overview = Data::BoostsOverview{ + .group = channel->isMegagroup(), .mine = slots ? int(slots->v.size()) : 0, .level = std::max(data.vlevel().v, 0), .boostCount = std::max( diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index f6e7feb40..83c3f1fed 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -1363,20 +1363,24 @@ void GiveawayInfoBox( ? start->quantity : (results->winnersCount + results->unclaimedCount); const auto months = start ? start->months : results->months; + const auto group = !start->channels.empty() + && start->channels.front()->isMegagroup(); text.append((finished ? tr::lng_prizes_end_text : tr::lng_prizes_how_text)( tr::now, lt_admins, - tr::lng_prizes_admins( - tr::now, - lt_count, - quantity, - lt_channel, - Ui::Text::Bold(first), - lt_duration, - TextWithEntities{ GiftDuration(months) }, - Ui::Text::RichLangValue), + (group + ? tr::lng_prizes_admins_group + : tr::lng_prizes_admins)( + tr::now, + lt_count, + quantity, + lt_channel, + Ui::Text::Bold(first), + lt_duration, + TextWithEntities{ GiftDuration(months) }, + Ui::Text::RichLangValue), Ui::Text::RichLangValue)); const auto many = start ? (start->channels.size() > 1) @@ -1387,8 +1391,12 @@ void GiveawayInfoBox( const auto all = start ? start->all : results->all; auto winners = all ? (many - ? tr::lng_prizes_winners_all_of_many - : tr::lng_prizes_winners_all_of_one)( + ? (group + ? tr::lng_prizes_winners_all_of_many_group + : tr::lng_prizes_winners_all_of_many) + : (group + ? tr::lng_prizes_winners_all_of_one_group + : tr::lng_prizes_winners_all_of_one))( tr::now, lt_count, count, @@ -1411,15 +1419,17 @@ void GiveawayInfoBox( ? results->additionalPrize : start->additionalPrize; if (!additionalPrize.isEmpty()) { - text.append("\n\n").append(tr::lng_prizes_additional_added( - tr::now, - lt_count, - count, - lt_channel, - Ui::Text::Bold(first), - lt_prize, - TextWithEntities{ additionalPrize }, - Ui::Text::RichLangValue)); + text.append("\n\n").append((group + ? tr::lng_prizes_additional_added_group + : tr::lng_prizes_additional_added)( + tr::now, + lt_count, + count, + lt_channel, + Ui::Text::Bold(first), + lt_prize, + TextWithEntities{ additionalPrize }, + Ui::Text::RichLangValue)); } const auto untilDate = start ? start->untilDate @@ -1448,18 +1458,25 @@ void GiveawayInfoBox( if (info.adminChannelId) { const auto channel = controller->session().data().channel( info.adminChannelId); - text.append("\n\n").append(tr::lng_prizes_how_no_admin( - tr::now, - lt_channel, - Ui::Text::Bold(channel->name()), - Ui::Text::RichLangValue)); + text.append("\n\n").append((channel->isMegagroup() + ? tr::lng_prizes_how_no_admin_group + : tr::lng_prizes_how_no_admin)( + tr::now, + lt_channel, + Ui::Text::Bold(channel->name()), + Ui::Text::RichLangValue)); } else if (info.tooEarlyDate) { - text.append("\n\n").append(tr::lng_prizes_how_no_joined( - tr::now, - lt_date, - Ui::Text::Bold( - langDateTime(base::unixtime::parse(info.tooEarlyDate))), - Ui::Text::RichLangValue)); + const auto channel = controller->session().data().channel( + info.adminChannelId); + text.append("\n\n").append((channel->isMegagroup() + ? tr::lng_prizes_how_no_joined_group + : tr::lng_prizes_how_no_joined)( + tr::now, + lt_date, + Ui::Text::Bold( + langDateTime( + base::unixtime::parse(info.tooEarlyDate))), + Ui::Text::RichLangValue)); } else if (!info.disallowedCountry.isEmpty()) { text.append("\n\n").append(tr::lng_prizes_how_no_country( tr::now, @@ -1499,7 +1516,9 @@ void GiveawayInfoBox( box.get(), object_ptr( box.get(), - tr::lng_prizes_cancelled(), + (group + ? tr::lng_prizes_cancelled_group() + : tr::lng_prizes_cancelled()), st::giveawayRefundedLabel), st::giveawayRefundedPadding), { padding.left(), 0, padding.right(), padding.bottom() }); diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp index 0129b8596..178945d32 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp @@ -313,21 +313,23 @@ void Controller::rowClicked(not_null row) { } } -object_ptr ReassignBoostFloodBox(int seconds) { +object_ptr ReassignBoostFloodBox(int seconds, bool group) { const auto days = seconds / 86400; const auto hours = seconds / 3600; const auto minutes = seconds / 60; return Ui::MakeInformBox({ - .text = tr::lng_boost_error_flood_text( - lt_left, - rpl::single(Ui::Text::Bold((days > 1) - ? tr::lng_days(tr::now, lt_count, days) - : (hours > 1) - ? tr::lng_hours(tr::now, lt_count, hours) - : (minutes > 1) - ? tr::lng_minutes(tr::now, lt_count, minutes) - : tr::lng_seconds(tr::now, lt_count, seconds))), - Ui::Text::RichLangValue), + .text = (group + ? tr::lng_boost_error_flood_text_group + : tr::lng_boost_error_flood_text)( + lt_left, + rpl::single(Ui::Text::Bold((days > 1) + ? tr::lng_days(tr::now, lt_count, days) + : (hours > 1) + ? tr::lng_hours(tr::now, lt_count, hours) + : (minutes > 1) + ? tr::lng_minutes(tr::now, lt_count, minutes) + : tr::lng_seconds(tr::now, lt_count, seconds))), + Ui::Text::RichLangValue), .title = tr::lng_boost_error_flood_title(), }); } @@ -445,7 +447,9 @@ object_ptr ReassignBoostsBox( const auto now = base::unixtime::now(); if (from.size() == 1 && from.front().cooldown > now) { cancel(); - return ReassignBoostFloodBox(from.front().cooldown - now); + return ReassignBoostFloodBox( + from.front().cooldown - now, + to->owner().peer(from.front().peerId)->isMegagroup()); } else if (from.size() == 1 && from.front().peerId) { return ReassignBoostSingleBox(to, from.front(), reassign, cancel); } diff --git a/Telegram/SourceFiles/data/data_boosts.h b/Telegram/SourceFiles/data/data_boosts.h index bada02a91..a5924bc3d 100644 --- a/Telegram/SourceFiles/data/data_boosts.h +++ b/Telegram/SourceFiles/data/data_boosts.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { struct BoostsOverview final { + bool group = false; int mine = 0; int level = 0; int boostCount = 0; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 006d2f87c..79fef7373 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -4796,11 +4796,13 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { auto prepareGiveawayLaunch = [&](const MTPDmessageActionGiveawayLaunch &action) { auto result = PreparedServiceText(); result.links.push_back(fromLink()); - result.text = tr::lng_action_giveaway_started( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); + result.text = (_history->peer->isMegagroup() + ? tr::lng_action_giveaway_started_group + : tr::lng_action_giveaway_started)( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); return result; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 6205011a4..159a0a4de 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -698,12 +698,27 @@ auto GenerateGiveawayStart( Ui::Text::Bold(tr::lng_prizes_participants(tr::now)), st::chatGiveawayPrizesTitleMargin); + const auto hasChannel = ranges::any_of( + data->channels, + &ChannelData::isBroadcast); + const auto hasGroup = ranges::any_of( + data->channels, + &ChannelData::isMegagroup); + const auto mixed = (hasChannel && hasGroup); pushText({ (data->all - ? tr::lng_prizes_participants_all - : tr::lng_prizes_participants_new)( - tr::now, - lt_count, - data->channels.size()), + ? (mixed + ? tr::lng_prizes_participants_all_mixed + : hasGroup + ? tr::lng_prizes_participants_all_group + : tr::lng_prizes_participants_all) + : (mixed + ? tr::lng_prizes_participants_new_mixed + : hasGroup + ? tr::lng_prizes_participants_new_group + : tr::lng_prizes_participants_new))( + tr::now, + lt_count, + data->channels.size()), }, st::chatGiveawayParticipantsMargin); auto list = ranges::views::all( diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index 01d6d5c5e..6c5583723 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -89,7 +89,8 @@ constexpr auto kAdditionalPrizeLengthMax = 128; void AddPremiumTopBarWithDefaultTitleBar( not_null box, rpl::producer<> showFinished, - rpl::producer titleText) { + rpl::producer titleText, + bool group) { struct State final { Ui::Animations::Simple animation; Ui::Text::String title; @@ -175,7 +176,9 @@ void AddPremiumTopBarWithDefaultTitleBar( st::startGiveawayCover, nullptr, tr::lng_giveaway_new_title(), - tr::lng_giveaway_new_about(Ui::Text::RichLangValue), + (group + ? tr::lng_giveaway_new_about_group + : tr::lng_giveaway_new_about)(Ui::Text::RichLangValue), true, false); bar->setAttribute(Qt::WA_TransparentForMouseEvents); @@ -265,6 +268,7 @@ void CreateGiveawayBox( rpl::variable confirmButtonBusy = true; }; + const auto group = peer->isMegagroup(); const auto state = box->lifetime().make_state(peer); const auto typeGroup = std::make_shared(); @@ -276,7 +280,8 @@ void CreateGiveawayBox( state->typeValue.value( ) | rpl::map(rpl::mappers::_1 == GiveawayType::Random), tr::lng_giveaway_start(), - tr::lng_giveaway_award())); + tr::lng_giveaway_award()), + peer->isMegagroup()); { const auto &padding = st::giveawayGiftCodeCoverDividerPadding; Ui::AddSkip(box->verticalLayout(), padding.bottom()); @@ -329,7 +334,8 @@ void CreateGiveawayBox( object_ptr( box, GiveawayType::Random, - tr::lng_giveaway_create_subtitle())); + tr::lng_giveaway_create_subtitle(), + group)); row->addRadio(typeGroup); row->setClickedCallback([=] { state->typeValue.force_assign(GiveawayType::Random); @@ -351,7 +357,8 @@ void CreateGiveawayBox( : tr::lng_giveaway_award_chosen( lt_count, rpl::single(selected.size()) | tr::to_count()); - }) | rpl::flatten_latest())); + }) | rpl::flatten_latest(), + group)); row->addRadio(typeGroup); row->setClickedCallback([=] { auto initBox = [=](not_null peersBox) { @@ -534,12 +541,14 @@ void CreateGiveawayBox( auto &list = state->selectedToSubscribe; list.erase(ranges::remove(list, peer), end(list)); }, box->lifetime()); - listState->controller.setTopStatus(tr::lng_giveaway_channels_this( - lt_count, - state->sliderValue.value( - ) | rpl::map([=](int v) -> float64 { - return state->apiOptions.giveawayBoostsPerPremium() * v; - }))); + listState->controller.setTopStatus((peer->isMegagroup() + ? tr::lng_giveaway_channels_this_group + : tr::lng_giveaway_channels_this)( + lt_count, + state->sliderValue.value( + ) | rpl::map([=](int v) -> float64 { + return state->apiOptions.giveawayBoostsPerPremium() * v; + }))); using IconType = Settings::IconType; Settings::AddButtonWithIcon( @@ -638,7 +647,8 @@ void CreateGiveawayBox( object_ptr( box, GiveawayType::AllMembers, - rpl::duplicate(subtitle))); + rpl::duplicate(subtitle), + group)); row->addRadio(membersGroup); row->setClickedCallback(createCallback(GiveawayType::AllMembers)); } @@ -646,14 +656,17 @@ void CreateGiveawayBox( object_ptr( box, GiveawayType::OnlyNewMembers, - std::move(subtitle))); + std::move(subtitle), + group)); row->addRadio(membersGroup); row->setClickedCallback(createCallback(GiveawayType::OnlyNewMembers)); Ui::AddSkip(countriesContainer); Ui::AddDividerText( countriesContainer, - tr::lng_giveaway_users_about()); + (group + ? tr::lng_giveaway_users_about_group() + : tr::lng_giveaway_users_about())); Ui::AddSkip(countriesContainer); } @@ -892,9 +905,11 @@ void CreateGiveawayBox( auto terms = object_ptr(dateContainer); terms->add(object_ptr( terms, - tr::lng_giveaway_date_about( - lt_count, - state->sliderValue.value() | tr::to_count()), + (group + ? tr::lng_giveaway_date_about_group + : tr::lng_giveaway_date_about)( + lt_count, + state->sliderValue.value() | tr::to_count()), st::boxDividerLabel)); Ui::AddSkip(terms.data()); Ui::AddSkip(terms.data()); @@ -907,9 +922,11 @@ void CreateGiveawayBox( } else { Ui::AddDividerText( dateContainer, - tr::lng_giveaway_date_about( - lt_count, - state->sliderValue.value() | tr::to_count())); + (group + ? tr::lng_giveaway_date_about_group + : tr::lng_giveaway_date_about)( + lt_count, + state->sliderValue.value() | tr::to_count())); Ui::AddSkip(dateContainer); } } @@ -1036,12 +1053,17 @@ void CreateGiveawayBox( } return false; }; + const auto group = peer->isMegagroup(); const auto title = isSpecific ? tr::lng_giveaway_awarded_title : tr::lng_giveaway_created_title; const auto body = isSpecific - ? tr::lng_giveaway_awarded_body - : tr::lng_giveaway_created_body; + ? (group + ? tr::lng_giveaway_awarded_body_group + : tr::lng_giveaway_awarded_body) + : (group + ? tr::lng_giveaway_created_body_group + : tr::lng_giveaway_created_body); show->showToast({ .text = Ui::Text::Bold( title(tr::now)).append('\n').append( diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp index 975dab5b3..4b5c7ac60 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp @@ -293,14 +293,13 @@ void MyChannelsListController::setCheckError(Fn callback) { std::unique_ptr MyChannelsListController::createRow( not_null channel) const { - if (channel->isMegagroup()) { - return nullptr; - } auto row = std::make_unique(channel); - row->setCustomStatus(tr::lng_chat_status_subscribers( - tr::now, - lt_count, - channel->membersCount())); + row->setCustomStatus((channel->isBroadcast() + ? tr::lng_chat_status_subscribers + : tr::lng_chat_status_members)( + tr::now, + lt_count, + channel->membersCount())); return row; } @@ -358,19 +357,18 @@ void SelectedChannelsListController::prepare() { std::unique_ptr SelectedChannelsListController::createRow( not_null channel) const { - if (channel->isMegagroup()) { - return nullptr; - } const auto isYourChannel = (_peer->asChannel() == channel); auto row = isYourChannel ? std::make_unique(channel) : std::make_unique(channel); row->setCustomStatus(isYourChannel ? QString() - : tr::lng_chat_status_subscribers( - tr::now, - lt_count, - channel->membersCount())); + : (channel->isMegagroup() + ? tr::lng_chat_status_members + : tr::lng_chat_status_subscribers)( + tr::now, + lt_count, + channel->membersCount())); return row; } diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp index 4417bfb25..c66a636ab 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp @@ -24,7 +24,8 @@ constexpr auto kColorIndexRandom = int(2); GiveawayTypeRow::GiveawayTypeRow( not_null parent, Type type, - rpl::producer subtitle) + rpl::producer subtitle, + bool group) : GiveawayTypeRow( parent, type, @@ -34,8 +35,12 @@ GiveawayTypeRow::GiveawayTypeRow( : (type == Type::Random) ? tr::lng_giveaway_create_option() : (type == Type::AllMembers) - ? tr::lng_giveaway_users_all() - : tr::lng_giveaway_users_new(), + ? (group + ? tr::lng_giveaway_users_all_group() + : tr::lng_giveaway_users_all()) + : (group + ? tr::lng_giveaway_users_new_group() + : tr::lng_giveaway_users_new()), std::move(subtitle), QImage()) { } diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.h b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.h index 9bb96812b..6cf12411d 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.h +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.h @@ -32,7 +32,8 @@ public: GiveawayTypeRow( not_null parent, Type type, - rpl::producer subtitle); + rpl::producer subtitle, + bool group); GiveawayTypeRow( not_null parent, diff --git a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp index fac7ff9f2..076b34532 100644 --- a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp +++ b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp @@ -128,7 +128,9 @@ void FillOverview( addSub( topRightLabel, stats.premiumMemberPercentage, - tr::lng_boosts_premium_audience); + (stats.group + ? tr::lng_boosts_premium_members + : tr::lng_boosts_premium_audience)); addSub( bottomLeftLabel, 0, @@ -248,7 +250,9 @@ void FillGetBoostsButton( (st.height + rect::m::sum::v(st.padding) - icon.height()) / 2, })->show(); Ui::AddSkip(content); - Ui::AddDividerText(content, tr::lng_boosts_get_boosts_subtext()); + Ui::AddDividerText(content, peer->isMegagroup() + ? tr::lng_boosts_get_boosts_subtext_group() + : tr::lng_boosts_get_boosts_subtext()); } } // namespace @@ -479,7 +483,9 @@ void InnerWidget::fill() { Ui::AddSkip(inner); Ui::AddSkip(inner); - Ui::AddDividerText(inner, tr::lng_boosts_list_subtext()); + Ui::AddDividerText(inner, status.overview.group + ? tr::lng_boosts_list_subtext_group() + : tr::lng_boosts_list_subtext()); } Ui::AddSkip(inner); @@ -488,7 +494,9 @@ void InnerWidget::fill() { Ui::AddSkip(inner, st::boostsLinkSkip); FillShareLink(inner, _show, status.link, _peer); Ui::AddSkip(inner); - Ui::AddDividerText(inner, tr::lng_boosts_link_subtext()); + Ui::AddDividerText(inner, status.overview.group + ? tr::lng_boosts_link_subtext_group() + : tr::lng_boosts_link_subtext()); FillGetBoostsButton(inner, _controller, _show, _peer, reloadOnDone); diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.cpp b/Telegram/SourceFiles/ui/boxes/boost_box.cpp index 5a226eea4..dc47020a2 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/boost_box.cpp @@ -165,9 +165,13 @@ void BoostBox( rpl::single(name)) : !counters.nextLevelBoosts ? tr::lng_boost_channel_title_max() - : !counters.level - ? tr::lng_boost_channel_title_first() - : tr::lng_boost_channel_title_more(); + : counters.level + ? (data.group + ? tr::lng_boost_channel_title_more_group() + : tr::lng_boost_channel_title_more()) + : (data.group + ? tr::lng_boost_channel_title_first_group() + : tr::lng_boost_channel_title_first()); }) | rpl::flatten_latest(); auto repeated = state->data.value( ) | rpl::map([=](BoostCounters counters) { @@ -189,25 +193,33 @@ void BoostBox( return (counters.mine || full) ? (left ? (!counters.level - ? tr::lng_boost_channel_you_first( - lt_count, - rpl::single(float64(left)), - Ui::Text::RichLangValue) - : tr::lng_boost_channel_you_more( - lt_count, - rpl::single(float64(left)), - lt_post, - std::move(post), - Ui::Text::RichLangValue)) + ? (data.group + ? tr::lng_boost_channel_you_first_group + : tr::lng_boost_channel_you_first)( + lt_count, + rpl::single(float64(left)), + Ui::Text::RichLangValue) + : (data.group + ? tr::lng_boost_channel_you_more_group + : tr::lng_boost_channel_you_more)( + lt_count, + rpl::single(float64(left)), + lt_post, + std::move(post), + Ui::Text::RichLangValue)) : (!counters.level - ? tr::lng_boost_channel_reached_first( - Ui::Text::RichLangValue) - : tr::lng_boost_channel_reached_more( - lt_count, - rpl::single(float64(counters.level)), - lt_post, - std::move(post), - Ui::Text::RichLangValue))) + ? (data.group + ? tr::lng_boost_channel_reached_first_group + : tr::lng_boost_channel_reached_first)( + Ui::Text::RichLangValue) + : (data.group + ? tr::lng_boost_channel_reached_more_group + : tr::lng_boost_channel_reached_more)( + lt_count, + rpl::single(float64(counters.level)), + lt_post, + std::move(post), + Ui::Text::RichLangValue))) : !counters.level ? tr::lng_boost_channel_needs_first( lt_count, @@ -244,6 +256,8 @@ void BoostBox( ? tr::lng_box_ok() : (counters.mine > 0) ? tr::lng_boost_again_button() + : data.group + ? tr::lng_boost_group_button() : tr::lng_boost_channel_button(); }) | rpl::flatten_latest(); @@ -408,9 +422,11 @@ object_ptr MakeLinkLabel( return result; } -void BoostBoxAlready(not_null box) { +void BoostBoxAlready(not_null box, bool group) { ConfirmBox(box, { - .text = tr::lng_boost_error_already_text(Text::RichLangValue), + .text = (group + ? tr::lng_boost_error_already_text_group + : tr::lng_boost_error_already_text)(Text::RichLangValue), .title = tr::lng_boost_error_already_title(), .inform = true, }); @@ -435,16 +451,23 @@ void GiftForBoostsBox( }); } -void GiftedNoBoostsBox(not_null box) { +void GiftedNoBoostsBox(not_null box, bool group) { InformBox(box, { - .text = tr::lng_boost_error_gifted_text(Text::RichLangValue), + .text = (group + ? tr::lng_boost_error_gifted_text_group + : tr::lng_boost_error_gifted_text)(Text::RichLangValue), .title = tr::lng_boost_error_gifted_title(), }); } -void PremiumForBoostsBox(not_null box, Fn buyPremium) { +void PremiumForBoostsBox( + not_null box, + bool group, + Fn buyPremium) { ConfirmBox(box, { - .text = tr::lng_boost_error_premium_text(Text::RichLangValue), + .text = (group + ? tr::lng_boost_error_premium_text_group + : tr::lng_boost_error_premium_text)(Text::RichLangValue), .confirmed = buyPremium, .confirmText = tr::lng_boost_error_premium_yes(), .title = tr::lng_boost_error_premium_title(), diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.h b/Telegram/SourceFiles/ui/boxes/boost_box.h index 68ff1d771..9acc046f7 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.h +++ b/Telegram/SourceFiles/ui/boxes/boost_box.h @@ -34,6 +34,7 @@ struct BoostBoxData { QString name; BoostCounters boost; bool allowMulti = false; + bool group = false; }; void BoostBox( @@ -41,14 +42,17 @@ void BoostBox( BoostBoxData data, Fn)> boost); -void BoostBoxAlready(not_null box); +void BoostBoxAlready(not_null box, bool group); void GiftForBoostsBox( not_null box, QString channel, int receive, bool again); -void GiftedNoBoostsBox(not_null box); -void PremiumForBoostsBox(not_null box, Fn buyPremium); +void GiftedNoBoostsBox(not_null box, bool group); +void PremiumForBoostsBox( + not_null box, + bool group, + Fn buyPremium); struct AskBoostChannelColor { int requiredLevel = 0; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 2f4dcc711..0e3c48cdb 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1046,10 +1046,9 @@ void Filler::addViewStatistics() { } }, &st::menuIconStats); } - if (!channel->isMegagroup() - && (canGetStats - || channel->amCreator() - || channel->canPostStories())) { + if (canGetStats + || channel->amCreator() + || channel->canPostStories()) { _addAction(tr::lng_boosts_title(tr::now), [=] { if (const auto strong = weak.get()) { controller->showSection(Info::Boosts::Make(peer)); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 8c936c92e..258da20c6 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -552,7 +552,7 @@ void SessionNavigation::showPeerByLinkResolved( } else { showPeerInfo(peer, params); } - } else if (resolveType == ResolveType::Boost && peer->isBroadcast()) { + } else if (resolveType == ResolveType::Boost && peer->isChannel()) { resolveBoostState(peer->asChannel()); } else { // Show specific posts only in channels / supergroups. @@ -631,6 +631,7 @@ void SessionNavigation::resolveBoostState(not_null channel) { .name = channel->name(), .boost = ParseBoostCounters(result), .allowMulti = (BoostsForGift(_session) > 0), + .group = channel->isMegagroup(), }, submit)); }).fail([=](const MTP::Error &error) { _boostStateResolving = nullptr; @@ -659,10 +660,12 @@ void SessionNavigation::applyBoost( uiShow()->show( Box(Ui::GiftForBoostsBox, name, receive, again)); } else { - uiShow()->show(Box(Ui::BoostBoxAlready)); + uiShow()->show( + Box(Ui::BoostBoxAlready, channel->isMegagroup())); } } else if (!_session->premium()) { - uiShow()->show(Box(Ui::PremiumForBoostsBox, [=] { + const auto group = channel->isMegagroup(); + uiShow()->show(Box(Ui::PremiumForBoostsBox, group, [=] { const auto id = peerToChannel(channel->id).bare; Settings::ShowPremium( parentController(), @@ -674,7 +677,8 @@ void SessionNavigation::applyBoost( uiShow()->show( Box(Ui::GiftForBoostsBox, name, receive, again)); } else { - uiShow()->show(Box(Ui::GiftedNoBoostsBox)); + uiShow()->show( + Box(Ui::GiftedNoBoostsBox, channel->isMegagroup())); } done({}); } else { From 180b14ea3680c23749fbbfaa4af80f18d79b1017 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 6 Feb 2024 14:54:15 +0400 Subject: [PATCH 25/73] Allow saving boosts to lift restrictions. --- Telegram/Resources/langs/lang.strings | 8 + Telegram/SourceFiles/boxes/boxes.style | 3 + .../boxes/peers/edit_peer_info_box.cpp | 38 ++- .../boxes/peers/edit_peer_permissions_box.cpp | 217 +++++++++++++++++- .../boxes/peers/edit_peer_permissions_box.h | 1 + Telegram/SourceFiles/data/data_channel.cpp | 61 ++++- Telegram/SourceFiles/data/data_channel.h | 12 +- 7 files changed, 317 insertions(+), 23 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b8fde1519..7e7679f6d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2181,6 +2181,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boost_channel_reached_more_group#other" = "This group reached **Level {count}** and can now {post}."; "lng_boost_channel_post_stories#one" = "post **{count} story** per day"; "lng_boost_channel_post_stories#other" = "post **{count} stories** per day"; +"lng_boost_channel_features" = "Your boosts will help {channel} to unlock new features."; +"lng_boost_group_lift_restrictions" = "Boost the group to remove messaging restrictions."; +"lng_boost_group_lift_restrictions_many#one" = "Boost the group **{count} times** to remove messaging restrictions."; +"lng_boost_group_lift_restrictions_many#other" = "Boost the group **{count} times** to remove messaging restrictions."; "lng_boost_error_gifted_title" = "Can't boost with gifted Premium!"; "lng_boost_error_gifted_text" = "Because your **Telegram Premium** subscription was gifted to you, you can't use it to boost channels."; "lng_boost_error_gifted_text_group" = "Because your **Telegram Premium** subscription was gifted to you, you can't use it to boost groups."; @@ -3566,6 +3570,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_rights_slowmode_interval_seconds#other" = "every {count} seconds"; "lng_rights_slowmode_interval_minutes#one" = "every {count} minute"; "lng_rights_slowmode_interval_minutes#other" = "every {count} minutes"; +"lng_rights_boosts_no_restrict" = "Do not restrict boosters"; +"lng_rights_boosts_about" = "Turn this on to always allow users who boosted your group to send messages and media."; +"lng_rights_boosts_about_on" = "Choose how many boosts a user must give to the group to bypass restrictions on sending messages."; "lng_slowmode_enabled"= "Slow mode is enabled. You can send your next message in {left}."; "lng_slowmode_no_many" = "Slow mode is enabled. You can't send more than one message at a time."; @@ -3661,6 +3668,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_restricted_send_gifs" = "The admins of this group restricted you from posting GIFs here."; "lng_restricted_send_inline" = "The admins of this group restricted you from posting inline content here."; "lng_restricted_send_polls" = "The admins of this group restricted you from posting polls here."; +"lng_restricted_boost_group" = "Boost this group to send messages"; "lng_restricted_send_message_until" = "The admins of this group restricted you from writing here until {date}, {time}."; "lng_restricted_send_photos_until" = "The admins of this group restricted you from posting photos here until {date}, {time}."; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index f43708f48..14a859e76 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -791,6 +791,9 @@ slowmodeLabelsMargin: margins(0px, 5px, 0px, 0px); slowmodeLabel: LabelSimple(defaultLabelSimple) { textFg: windowSubTextFg; } +boostsUnrestrictLabel: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; +} customBadgeField: InputField(defaultInputField) { textMargins: margins(2px, 7px, 2px, 0px); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index fba0c7303..8ae5907e6 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -198,6 +198,37 @@ void SaveSlowmodeSeconds( api->registerModifyRequest(key, requestId); } +void SaveBoostsUnrestrict( + not_null channel, + int boostsUnrestrict, + Fn done) { + const auto api = &channel->session().api(); + const auto key = Api::RequestKey("boosts_unrestrict", channel->id); + const auto requestId = api->request( + MTPchannels_SetBoostsToUnblockRestrictions( + channel->inputChannel, + MTP_int(boostsUnrestrict)) + ).done([=](const MTPUpdates &result) { + api->clearModifyRequest(key); + api->applyUpdates(result); + channel->setBoostsUnrestrict( + channel->boostsApplied(), + boostsUnrestrict); + done(); + }).fail([=](const MTP::Error &error) { + api->clearModifyRequest(key); + if (error.type() != u"CHAT_NOT_MODIFIED"_q) { + return; + } + channel->setBoostsUnrestrict( + channel->boostsApplied(), + boostsUnrestrict); + done(); + }).send(); + + api->registerModifyRequest(key, requestId); +} + void ShowEditPermissions( not_null navigation, not_null peer) { @@ -215,6 +246,10 @@ void ShowEditPermissions( close); if (const auto channel = peer->asChannel()) { SaveSlowmodeSeconds(channel, result.slowmodeSeconds, close); + SaveBoostsUnrestrict( + channel, + result.boostsUnrestrict, + close); } }; auto done = [=](EditPeerPermissionsBoxResult result) { @@ -225,7 +260,8 @@ void ShowEditPermissions( const auto saveFor = peer->migrateToOrMe(); const auto chat = saveFor->asChat(); - if (!result.slowmodeSeconds || !chat) { + if (!chat + || (!result.slowmodeSeconds && !result.boostsUnrestrict)) { save(saveFor, result); return; } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index f5024859c..36204506e 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -8,8 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_peer_permissions_box.h" #include "lang/lang_keys.h" +#include "core/ui_integration.h" +#include "data/stickers/data_custom_emoji.h" #include "data/data_channel.h" #include "data/data_chat.h" +#include "data/data_session.h" #include "ui/effects/toggle_arrow.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" @@ -34,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_common.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" +#include "styles/style_chat.h" #include "styles/style_info.h" #include "styles/style_menu_icons.h" #include "styles/style_window.h" @@ -42,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { constexpr auto kSlowmodeValues = 7; +constexpr auto kBoostsUnrestrictValues = 5; constexpr auto kSuggestGigagroupThreshold = 199000; constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000); @@ -163,6 +168,10 @@ int SlowmodeDelayByIndex(int index) { Unexpected("Index in SlowmodeDelayByIndex."); } +[[nodiscard]] int BoostsUnrestrictByIndex(int index) { + return index + 1; +} + template void ApplyDependencies( const CheckboxesMap &checkboxes, @@ -768,14 +777,14 @@ void AddSlowmodeLabels( } } -Fn AddSlowmodeSlider( +rpl::producer AddSlowmodeSlider( not_null container, not_null peer) { using namespace rpl::mappers; if (const auto chat = peer->asChat()) { if (!chat->amCreator()) { - return [] { return 0; }; + return rpl::single(0); } } const auto channel = peer->asChannel(); @@ -783,10 +792,6 @@ Fn AddSlowmodeSlider( const auto secondsCount = lifetime.make_state>( channel ? channel->slowmodeSeconds() : 0); - container->add( - object_ptr(container), - { 0, st::infoProfileSkip, 0, st::infoProfileSkip }); - container->add( object_ptr( container, @@ -856,7 +861,157 @@ Fn AddSlowmodeSlider( st::proxyAboutPadding), style::margins(0, st::infoProfileSkip, 0, st::infoProfileSkip)); - return [=] { return secondsCount->current(); }; + return secondsCount->value(); +} + +void AddBoostsUnrestrictLabels( + not_null container, + not_null session) { + const auto labels = container->add( + object_ptr(container, st::normalFont->height), + st::slowmodeLabelsMargin); + const auto manager = &session->data().customEmojiManager(); + const auto one = Ui::Text::SingleCustomEmoji( + manager->registerInternalEmoji( + st::boostMessageIcon, + st::boostMessageIconPadding)); + const auto many = Ui::Text::SingleCustomEmoji( + manager->registerInternalEmoji( + st::boostsMessageIcon, + st::boostsMessageIconPadding)); + const auto context = Core::MarkedTextContext{ + .session = session, + .customEmojiRepaint = [] {}, + .customEmojiLoopLimit = 1, + }; + for (auto i = 0; i != kBoostsUnrestrictValues; ++i) { + const auto label = Ui::CreateChild( + labels, + st::boostsUnrestrictLabel); + label->setMarkedText( + TextWithEntities(i ? one : many).append(QString::number(i + 1)), + context); + rpl::combine( + labels->widthValue(), + label->widthValue() + ) | rpl::start_with_next([=](int outer, int inner) { + const auto skip = st::localStorageLimitMargin; + const auto size = st::localStorageLimitSlider.seekSize; + const auto available = outer + - skip.left() + - skip.right() + - size.width(); + const auto shift = (i == 0) + ? -(size.width() / 2) + : (i + 1 == kBoostsUnrestrictValues) + ? (size.width() - (size.width() / 2) - inner) + : (-inner / 2); + const auto left = skip.left() + + (size.width() / 2) + + (i * available) / (kBoostsUnrestrictValues - 1) + + shift; + label->moveToLeft(left, 0, outer); + }, label->lifetime()); + } +} + +rpl::producer AddBoostsUnrestrictSlider( + not_null container, + not_null peer) { + using namespace rpl::mappers; + + if (const auto chat = peer->asChat()) { + if (!chat->amCreator()) { + return rpl::single(0); + } + } + const auto channel = peer->asChannel(); + auto &lifetime = container->lifetime(); + const auto boostsUnrestrict = lifetime.make_state>( + channel ? channel->boostsUnrestrict() : 0); + + container->add( + object_ptr(container), + { 0, st::infoProfileSkip, 0, st::infoProfileSkip }); + + auto enabled = boostsUnrestrict->value( + ) | rpl::map(_1 > 0); + container->add(object_ptr( + container, + tr::lng_rights_boosts_no_restrict(), + st::defaultSettingsButton + ))->toggleOn(rpl::duplicate(enabled))->toggledValue( + ) | rpl::start_with_next([=](bool toggled) { + if (toggled && !boostsUnrestrict->current()) { + *boostsUnrestrict = 1; + } else if (!toggled && boostsUnrestrict->current()) { + *boostsUnrestrict = 0; + } + }, container->lifetime()); + + const auto outer = container->add( + object_ptr>( + container, + object_ptr(container))); + outer->toggleOn(rpl::duplicate(enabled), anim::type::normal); + outer->finishAnimating(); + + const auto inner = outer->entity(); + + AddBoostsUnrestrictLabels(inner, &peer->session()); + + const auto slider = inner->add( + object_ptr(inner, st::localStorageLimitSlider), + st::localStorageLimitMargin); + slider->resize(st::localStorageLimitSlider.seekSize); + slider->setPseudoDiscrete( + kBoostsUnrestrictValues, + BoostsUnrestrictByIndex, + boostsUnrestrict->current(), + [=](int boosts) { + (*boostsUnrestrict) = boosts; + }); + + inner->add( + object_ptr( + inner, + object_ptr( + inner, + rpl::conditional( + boostsUnrestrict->value() | rpl::map(_1 > 0), + tr::lng_rights_boosts_about_on(), + tr::lng_rights_boosts_about()), + st::boxDividerLabel), + st::proxyAboutPadding), + style::margins(0, st::infoProfileSkip, 0, 0)); + + return boostsUnrestrict->value(); +} + +rpl::producer AddBoostsUnrestrictWrapped( + not_null container, + not_null peer, + rpl::producer shown) { + const auto wrap = container->add( + object_ptr>( + container, + object_ptr(container))); + wrap->toggleOn(rpl::duplicate(shown), anim::type::normal); + wrap->finishAnimating(); + + auto result = AddBoostsUnrestrictSlider(wrap->entity(), peer); + const auto divider = container->add( + object_ptr>( + container, + object_ptr(container), + QMargins{ 0, st::infoProfileSkip, 0, st::infoProfileSkip })); + divider->toggleOn(rpl::combine( + std::move(shown), + rpl::duplicate(result), + !rpl::mappers::_1 || !rpl::mappers::_2)); + divider->finishAnimating(); + + return result; } void AddSuggestGigagroup( @@ -983,7 +1138,40 @@ void ShowEditPeerPermissionsBox( inner->add(std::move(checkboxes)); - const auto getSlowmodeSeconds = AddSlowmodeSlider(inner, peer); + struct State { + rpl::variable slowmodeSeconds; + rpl::variable boostsUnrestrict; + rpl::variable hasSendRestrictions; + }; + static constexpr auto kSendRestrictions = Flag::EmbedLinks + | Flag::SendGames + | Flag::SendGifs + | Flag::SendInline + | Flag::SendPolls + | Flag::SendStickers + | Flag::SendPhotos + | Flag::SendVideos + | Flag::SendVideoMessages + | Flag::SendMusic + | Flag::SendVoiceMessages + | Flag::SendFiles + | Flag::SendOther; + const auto state = inner->lifetime().make_state(); + state->hasSendRestrictions = ((restrictions & kSendRestrictions) != 0) + || (peer->isChannel() && peer->asChannel()->slowmodeSeconds() > 0); + state->boostsUnrestrict = AddBoostsUnrestrictWrapped( + inner, + peer, + state->hasSendRestrictions.value()); + state->slowmodeSeconds = AddSlowmodeSlider(inner, peer); + state->hasSendRestrictions = rpl::combine( + rpl::single( + restrictions + ) | rpl::then(std::move(changes)), + state->slowmodeSeconds.value() + ) | rpl::map([](ChatRestrictions restrictions, int slowmodeSeconds) { + return ((restrictions & kSendRestrictions) != 0) || slowmodeSeconds; + }); if (const auto channel = peer->asChannel()) { if (channel->amCreator() @@ -999,7 +1187,18 @@ void ShowEditPeerPermissionsBox( AddBannedButtons(inner, navigation, peer); box->addButton(tr::lng_settings_save(), [=, rights = getRestrictions] { - done({ rights(), getSlowmodeSeconds() }); + const auto restrictions = rights(); + const auto slowmodeSeconds = state->slowmodeSeconds.current(); + const auto hasRestrictions = (slowmodeSeconds > 0) + || ((restrictions & kSendRestrictions) != 0); + const auto boostsUnrestrict = hasRestrictions + ? state->boostsUnrestrict.current() + : 0; + done({ + restrictions, + slowmodeSeconds, + boostsUnrestrict, + }); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h index c1ce0eee1..05e1962d4 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h @@ -37,6 +37,7 @@ class SessionNavigation; struct EditPeerPermissionsBoxResult final { ChatRestrictions rights; int slowmodeSeconds = 0; + int boostsUnrestrict = 0; }; void ShowEditPeerPermissionsBox( diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index e130dac18..39cd1a1ca 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -772,34 +772,74 @@ void ChannelData::setMigrateFromChat(ChatData *chat) { } int ChannelData::slowmodeSeconds() const { - return _slowmodeSeconds; + if (const auto info = mgInfo.get()) { + return info->slowmodeSeconds; + } + return 0; } void ChannelData::setSlowmodeSeconds(int seconds) { - if (_slowmodeSeconds == seconds) { + if (!mgInfo || slowmodeSeconds() == seconds) { return; } - _slowmodeSeconds = seconds; + mgInfo->slowmodeSeconds = seconds; session().changes().peerUpdated(this, UpdateFlag::Slowmode); } TimeId ChannelData::slowmodeLastMessage() const { - return (hasAdminRights() || amCreator()) ? 0 : _slowmodeLastMessage; + return (hasAdminRights() || amCreator() || !mgInfo) + ? 0 + : mgInfo->slowmodeLastMessage; } void ChannelData::growSlowmodeLastMessage(TimeId when) { + const auto info = mgInfo.get(); const auto now = base::unixtime::now(); accumulate_min(when, now); - if (_slowmodeLastMessage > now) { - _slowmodeLastMessage = when; - } else if (_slowmodeLastMessage >= when) { + if (!info) { + return; + } else if (info->slowmodeLastMessage > now) { + info->slowmodeLastMessage = when; + } else if (info->slowmodeLastMessage >= when) { return; } else { - _slowmodeLastMessage = when; + info->slowmodeLastMessage = when; } session().changes().peerUpdated(this, UpdateFlag::Slowmode); } +int ChannelData::boostsApplied() const { + if (const auto info = mgInfo.get()) { + return info->boostsApplied; + } + return 0; +} + +int ChannelData::boostsUnrestrict() const { + if (const auto info = mgInfo.get()) { + return info->boostsUnrestrict; + } + return 0; +} + +void ChannelData::setBoostsUnrestrict(int applied, int unrestrict) { + if (const auto info = mgInfo.get()) { + if (info->boostsApplied == applied + && info->boostsUnrestrict == unrestrict) { + return; + } + const auto wasUnrestricted = (info->boostsApplied > 0) + && (info->boostsApplied >= info->boostsUnrestrict); + const auto nowUnrestricted = (applied > 0) + && (applied >= unrestrict); + info->boostsApplied = applied; + info->boostsUnrestrict = unrestrict; + if (wasUnrestricted != nowUnrestricted) { + session().changes().peerUpdated(this, UpdateFlag::Rights); + } + } +} + void ChannelData::setInvitePeek(const QString &hash, TimeId expires) { if (!_invitePeek) { _invitePeek = std::make_unique(); @@ -1118,8 +1158,9 @@ void ApplyChannelUpdate( if (stickersChanged) { session->changes().peerUpdated(channel, UpdateFlag::StickersSet); } - - channel->mgInfo->boostsApplied = update.vboosts_applied().value_or_empty(); + channel->setBoostsUnrestrict( + update.vboosts_applied().value_or_empty(), + update.vboosts_unrestrict().value_or_empty()); } channel->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty())); channel->setTranslationDisabled(update.is_translations_disabled()); diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 680627e0e..cd12c8a9e 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -133,6 +133,10 @@ public: mutable int lastParticipantsStatus = LastParticipantsUpToDate; int lastParticipantsCount = 0; int boostsApplied = 0; + int boostsUnrestrict = 0; + + int slowmodeSeconds = 0; + TimeId slowmodeLastMessage = 0; private: ChatData *_migratedFrom = nullptr; @@ -433,6 +437,11 @@ public: [[nodiscard]] TimeId slowmodeLastMessage() const; void growSlowmodeLastMessage(TimeId when); + [[nodiscard]] int boostsApplied() const; + [[nodiscard]] int boostsUnrestrict() const; + [[nodiscard]] bool unrestrictedByBoosts() const; + void setBoostsUnrestrict(int applied, int unrestrict); + void setInvitePeek(const QString &hash, TimeId expires); void clearInvitePeek(); [[nodiscard]] TimeId invitePeekExpires() const; @@ -520,9 +529,6 @@ private: std::unique_ptr _call; PeerId _callDefaultJoinAs = 0; - int _slowmodeSeconds = 0; - TimeId _slowmodeLastMessage = 0; - }; namespace Data { From ea12c2f62ce730d9333f6db83d0aae1a775ff828 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 6 Feb 2024 17:32:25 +0400 Subject: [PATCH 26/73] Respect boosts restrictions lifting. --- Telegram/Resources/langs/lang.strings | 4 +-- .../boxes/peers/edit_peer_permissions_box.cpp | 2 +- Telegram/SourceFiles/data/data_channel.cpp | 31 +++++++++++++++---- Telegram/SourceFiles/data/data_channel.h | 2 ++ Telegram/SourceFiles/data/data_peer.cpp | 3 +- .../SourceFiles/data/data_peer_values.cpp | 4 ++- 6 files changed, 35 insertions(+), 11 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7e7679f6d..aa05e9541 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1742,8 +1742,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_giveaway_results#other" = "{count} winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes."; "lng_action_giveaway_results_some" = "Some winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes."; "lng_action_giveaway_results_none" = "No winners of the giveaway could be selected."; -"lng_action_boost_apply#one" = "{from} boosted the group."; -"lng_action_boost_apply#other" = "{from} boosted the group {count} times."; +"lng_action_boost_apply#one" = "{from} boosted the group"; +"lng_action_boost_apply#other" = "{from} boosted the group {count} times"; "lng_similar_channels_title" = "Similar channels"; "lng_similar_channels_view_all" = "View all"; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index 36204506e..9b0747870 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -889,7 +889,7 @@ void AddBoostsUnrestrictLabels( labels, st::boostsUnrestrictLabel); label->setMarkedText( - TextWithEntities(i ? one : many).append(QString::number(i + 1)), + TextWithEntities(i ? many : one).append(QString::number(i + 1)), context); rpl::combine( labels->widthValue(), diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 39cd1a1ca..cf9048a5b 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -787,7 +787,10 @@ void ChannelData::setSlowmodeSeconds(int seconds) { } TimeId ChannelData::slowmodeLastMessage() const { - return (hasAdminRights() || amCreator() || !mgInfo) + return (hasAdminRights() + || amCreator() + || unrestrictedByBoosts() + || !mgInfo) ? 0 : mgInfo->slowmodeLastMessage; } @@ -822,20 +825,36 @@ int ChannelData::boostsUnrestrict() const { return 0; } +bool ChannelData::unrestrictedByBoosts() const { + if (const auto info = mgInfo.get()) { + return (info->boostsUnrestrict > 0) + && (info->boostsApplied >= info->boostsUnrestrict); + } + return 0; +} + +rpl::producer ChannelData::unrestrictedByBoostsValue() const { + return mgInfo + ? mgInfo->unrestrictedByBoostsChanges.events_starting_with( + unrestrictedByBoosts()) + : (rpl::single(false) | rpl::type_erased()); +} + void ChannelData::setBoostsUnrestrict(int applied, int unrestrict) { if (const auto info = mgInfo.get()) { if (info->boostsApplied == applied && info->boostsUnrestrict == unrestrict) { return; } - const auto wasUnrestricted = (info->boostsApplied > 0) - && (info->boostsApplied >= info->boostsUnrestrict); - const auto nowUnrestricted = (applied > 0) - && (applied >= unrestrict); + const auto wasUnrestricted = unrestrictedByBoosts(); info->boostsApplied = applied; info->boostsUnrestrict = unrestrict; + const auto nowUnrestricted = unrestrictedByBoosts(); if (wasUnrestricted != nowUnrestricted) { - session().changes().peerUpdated(this, UpdateFlag::Rights); + info->unrestrictedByBoostsChanges.fire_copy(nowUnrestricted); + session().changes().peerUpdated( + this, + UpdateFlag::Rights | UpdateFlag::Slowmode); } } } diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index cd12c8a9e..b822edda5 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -114,6 +114,7 @@ public: base::flat_map, Restricted> lastRestricted; base::flat_set> markupSenders; base::flat_set> bots; + rpl::event_stream unrestrictedByBoostsChanges; // For admin badges, full admins list with ranks. base::flat_map admins; @@ -440,6 +441,7 @@ public: [[nodiscard]] int boostsApplied() const; [[nodiscard]] int boostsUnrestrict() const; [[nodiscard]] bool unrestrictedByBoosts() const; + [[nodiscard]] rpl::producer unrestrictedByBoostsValue() const; void setBoostsUnrestrict(int applied, int unrestrict); void setInvitePeek(const QString &hash, TimeId expires); diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 1a0abf134..78c2051a0 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -1122,7 +1122,8 @@ Data::RestrictionCheckResult PeerData::amRestricted( : ChatRestrictions(0)); return (channel->amCreator() || allowByAdminRights(right, channel)) ? Result::Allowed() - : (defaultRestrictions & right) + : ((defaultRestrictions & right) + && !channel->unrestrictedByBoosts()) ? Result::WithEveryone() : (channel->restrictions() & right) ? Result::Explicit() diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index 4a69ae3db..0d5393ac8 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -267,11 +267,13 @@ inline auto DefaultRestrictionValue( AdminRightValue( channel, ChatAdminRight::PostMessages), + channel->unrestrictedByBoostsValue(), RestrictionsValue(channel, rights), DefaultRestrictionsValue(channel, rights), [=]( ChannelDataFlags flags, bool postMessagesRight, + bool unrestrictedByBoosts, ChatRestrictions sendRestriction, ChatRestrictions defaultSendRestriction) { const auto notAmInFlags = Flag::Left | Flag::Forbidden; @@ -281,7 +283,7 @@ inline auto DefaultRestrictionValue( || ((flags & Flag::HasLink) && !(flags & Flag::JoinToWrite)); const auto restricted = sendRestriction - | defaultSendRestriction; + | (defaultSendRestriction && !unrestrictedByBoosts); return allowed && !forumRestriction && (postMessagesRight From 680171226cddc3c5873f19e9557e15f3c7816b87 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 7 Feb 2024 12:20:59 +0400 Subject: [PATCH 27/73] Boost community features list. --- .../settings/premium/features/background.png | Bin 0 -> 734 bytes .../premium/features/background@2x.png | Bin 0 -> 1577 bytes .../premium/features/background@3x.png | Bin 0 -> 2103 bytes .../premium/features/custom_background.png | Bin 0 -> 771 bytes .../premium/features/custom_background@2x.png | Bin 0 -> 1803 bytes .../premium/features/custom_background@3x.png | Bin 0 -> 2395 bytes .../premium/features/custom_emoji.png | Bin 0 -> 914 bytes .../premium/features/custom_emoji@2x.png | Bin 0 -> 1967 bytes .../premium/features/custom_emoji@3x.png | Bin 0 -> 3382 bytes .../settings/premium/features/custom_link.png | Bin 0 -> 712 bytes .../premium/features/custom_link@2x.png | Bin 0 -> 1715 bytes .../premium/features/custom_link@3x.png | Bin 0 -> 2455 bytes .../premium/features/custom_reactions.png | Bin 0 -> 668 bytes .../premium/features/custom_reactions@2x.png | Bin 0 -> 1577 bytes .../premium/features/custom_reactions@3x.png | Bin 0 -> 2280 bytes .../premium/features/emoji_status.png | Bin 0 -> 723 bytes .../premium/features/emoji_status@2x.png | Bin 0 -> 1786 bytes .../premium/features/emoji_status@3x.png | Bin 0 -> 2551 bytes .../icons/settings/premium/features/link.png | Bin 0 -> 639 bytes .../settings/premium/features/link@2x.png | Bin 0 -> 1489 bytes .../settings/premium/features/link@3x.png | Bin 0 -> 1986 bytes .../icons/settings/premium/features/name.png | Bin 0 -> 841 bytes .../settings/premium/features/name@2x.png | Bin 0 -> 1891 bytes .../settings/premium/features/name@3x.png | Bin 0 -> 2481 bytes .../settings/premium/features/stories.png | Bin 0 -> 806 bytes .../settings/premium/features/stories@2x.png | Bin 0 -> 1789 bytes .../settings/premium/features/stories@3x.png | Bin 0 -> 2504 bytes .../settings/premium/features/transcribe.png | Bin 0 -> 705 bytes .../premium/features/transcribe@2x.png | Bin 0 -> 1403 bytes .../premium/features/transcribe@3x.png | Bin 0 -> 2400 bytes Telegram/Resources/langs/lang.strings | 47 +++- Telegram/SourceFiles/api/api_peer_colors.cpp | 45 +++- Telegram/SourceFiles/api/api_peer_colors.h | 14 +- .../boxes/peers/edit_peer_color_box.cpp | 10 +- .../boxes/peers/replace_boost_box.cpp | 44 +++ .../boxes/peers/replace_boost_box.h | 6 + Telegram/SourceFiles/ui/boxes/boost_box.cpp | 251 +++++++++++++++--- Telegram/SourceFiles/ui/boxes/boost_box.h | 13 + Telegram/SourceFiles/ui/effects/premium.style | 29 +- .../window/window_session_controller.cpp | 1 + 40 files changed, 401 insertions(+), 59 deletions(-) create mode 100644 Telegram/Resources/icons/settings/premium/features/background.png create mode 100644 Telegram/Resources/icons/settings/premium/features/background@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/background@3x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/custom_background.png create mode 100644 Telegram/Resources/icons/settings/premium/features/custom_background@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/custom_background@3x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/custom_emoji.png create mode 100644 Telegram/Resources/icons/settings/premium/features/custom_emoji@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/custom_emoji@3x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/custom_link.png create mode 100644 Telegram/Resources/icons/settings/premium/features/custom_link@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/custom_link@3x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/custom_reactions.png create mode 100644 Telegram/Resources/icons/settings/premium/features/custom_reactions@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/custom_reactions@3x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/emoji_status.png create mode 100644 Telegram/Resources/icons/settings/premium/features/emoji_status@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/emoji_status@3x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/link.png create mode 100644 Telegram/Resources/icons/settings/premium/features/link@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/link@3x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/name.png create mode 100644 Telegram/Resources/icons/settings/premium/features/name@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/name@3x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/stories.png create mode 100644 Telegram/Resources/icons/settings/premium/features/stories@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/stories@3x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/transcribe.png create mode 100644 Telegram/Resources/icons/settings/premium/features/transcribe@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/features/transcribe@3x.png diff --git a/Telegram/Resources/icons/settings/premium/features/background.png b/Telegram/Resources/icons/settings/premium/features/background.png new file mode 100644 index 0000000000000000000000000000000000000000..72d93ef9c186f195f10ef7252f719fe847271b8e GIT binary patch literal 734 zcmV<40wMj0P)Tl`%+iQ5c5b+r0!)bR~isa%d=VaA`?M8iIz1K#UgC z(b2#$XljaVa%!k0kZ4FFYHDf=gOCuyMT#pD5rh%fymEd2;l$DZsu6$xJAL<@^F8-F z@A>Yzirejee}BjCG(0@~OF~PEuItrmwd>sM?5xY>YBU3nR4SF^xUTEa zb-fcYJw5&O^u%wKN~PLCntVtk5?fnau~;mV$y{GwkB^T>qtQyGqG_6_1wf+yKpFpOj}*;W;>wzfv{?(WX(_4<51l8cLrUH3aSHYS2xE?2A7B&1J7p->3G z%*@Qg!-L=N7cO*CHa0e%pPx@oP5|Wdc~w>A)EB5;uh;8!5u2t7U~q7-6J!|1$H&Lx z<0F8fp&^+9;L9W1wuK7-NJ>*2%4V|@6BFW;+skQH^mEBxv2FWTpkC?;5*z$Rjs-nI zT|ENO-`_8SpL!(v(4nKFBhxfjR#pI5metqSO78*^wUo_-?t#0 zpin4e+qP+%Brh&5NEQkOlH1$c^7W^urzCH0Z=IlYIxRn()zww;eb3F!0odH!B)Pl0 z>u})a=H~6~%?aAy-zPaaISF8Sd70$W(vr-PC)KWWIxRb(1@d@2wOWm2Fc=&d7$A9k zd^|cj+McPZs;a8uDCXzqNuHgZIY9uXrlxjwb^?I_fLJUhcK7-D*=#o3?=#!BEz2TV zEEZ+1uIoLo+CU%>4u@SXmt|REMAV`vil%9WLSb)jPfm5cX8%#j|N0&M0bM$1l78Oh Qvj6}907*qoM6N<$g0i7n;Q#;t literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/background@2x.png b/Telegram/Resources/icons/settings/premium/features/background@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c2b1609eac60f2dc7b260a5486b649dd2003128f GIT binary patch literal 1577 zcmV+^2G;qBP)O9 zi4Q7@Ej>sH7EuyG6dJ5h4@o{)F1?i6AA= zYX-g69neY^kjZ4SSS;Du*@Tc#C=|hy=XpwLFc{phVT0fApP8BY^y$;Y#6+atWm%4k zrlFx>czBquRHLJ#H*enDxN+mMFtG#iJb(ZG{S~8V)!p5lnwn||WE`>s2M(M(dD3h) zKYH}2ySp2WXV0FkuCB&*Pft%@U!ULar<4*xR8>XdP$+qP{U zkB3tF?%g|`YinyO0v$VcEOwyl*RSJU-@biIOG`uJ&Ye4Pl+Mo1aG)iLF(DxV1LStQ zmm7mp8Y4||a&m2LEw=OW@@zI6wnb4C1OWi>RnTb(kgBTa#3B{Wf$vT%3I>DUzkg@{ zPEAe4a@Rlnbj;1o+3ohh!NG-vh34kw-rioc3xa@gFx0=@dwl-<`N)wY>FMdi!^6zT z^Zc3t0f5)*^?JQpOE4J3g<-fH*WhbUDMcW|<+$#)AO8bI(;5J=Y-gzWfbcu;AAxW~ zvN}MkrBwrp{BVZax^=65$cTwC?iq>;$mw(r4Gq0|^(r?vml;_eS{ejckiV=V2Car^e|xR-nS z^r_B`?HEf+N&vuWwSN8jl~Ou6Iayg*sr77WX<=LPnwlB_$j!}VrQF!qSbcr{=;$b= z^x?yY1~7&|j~_qQ;orG)2YW0kDzaEC@>0y0vA?mfydBU%q@9JuhCo*wWI% zZuEFO<>lq~?%g|l_%I3w3iwi8<%~007;U1dV0_^dKr|F zkug0zZODH2?p-$I`}glzO@p%8Y(IbgR1}4UEQ0pz*)uykODXN>=m-Z|5=(Zrg5|ST zi;IicaJgKSm6aZkM|<_fix-1~gGotABO@bGfxKR?q9{p8NzoId&*z(;p9g@VqN1v* zDs9w${P=;5wzf8Hal&CJ3B93 zx`ZF+!otEgZ{A=WeLmm7z(6n<1OT4rRaNDAUJ!&pAb<{N!45f&Lwi9%0p=|Lu-olU zr!)EtF^QsRHk)RYN%*=!*p17naiZ+QypP|UwyLWFxL&N_4`(;@c1OeX^c7mq$ z#B(&(R#o-auU{WNe7Jr4cKGSa%7IWRDJd%h(&dYAptw(A|EcunL+e88?Z+2lHk;Sg b&r^Q@C=eiIof2SJ00000NkvXXu0mjfvr7nN literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/background@3x.png b/Telegram/Resources/icons/settings/premium/features/background@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..4eeae2a994e4082657519c46e2066bfcfb576df4 GIT binary patch literal 2103 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/custom_background@3x.png b/Telegram/Resources/icons/settings/premium/features/custom_background@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..dd6a63388d68b52e882aa604abbe562b1c32e8ac GIT binary patch literal 2395 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 GFaHP6X^n0G literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/custom_link.png b/Telegram/Resources/icons/settings/premium/features/custom_link.png new file mode 100644 index 0000000000000000000000000000000000000000..7faddf7061e79a7543d8b93f9e0c1bad3aa93c26 GIT binary patch literal 712 zcmV;(0yq7MP)Tm9a}|aTLeT^>^haVwaXAxh6EIOF_5^BB6$gDT1R@ zON&eX11<^T;2;!)a3NZnB3yJ3Tq@8-5f$9TS_&L&G6_$^@4b7k_ukig(fe-q{Lb&g z`JQw5UB+&=zrDS|&$Pb2{zHN+!t?z5`}@~Po12@5hllZa-0gNRm&=!z7gYk!^T6}` z*OX$hh^xB4zjwJ@-vF(xt+m^2M7+DZi$o$jJ3AhaC!fz#oY(99C(y>m#&9@9#AGt5 zb^7}H`tk80_4q4Lzu!m1U@)kADhL81Rw|XhgN~1nqtWP}TmwL}*+fKx!JrSaTCJz2 zr@DJ#Hk*~7*=!aOx3{+`$e=vqIIh#_lu9L&$)t=ELPn#JD2mD;!!Q7Vh#=ehtzNH( zh~aQpdBJEj(tfGB-e@%Rt=-<va0)c=sZnN1CQL0e}c|0COEEEdLY?fsQ zgMoH-EXz(NljU-$Gk#GNr8=p$wzkIOv9=bA#nSC|5z+7WOF-GBH z-X0AKf}jHZYRbjM1pwr7IW1_hSXe9;n!Q*o0O0KGtX8Y3^Q7@tt5rmd#bUa3U0z;t z9LKWk(a{kdLOPvxI-P230KhN|eKc-vZVnC(1VPx{-90})Z@1fYi2)#$N+}2yw~(}#zLPN$=? unaN~Ap->``(DvthB7{h0B{Bc&ck~HdSUiGANEDy|0000%Pru)9u~=TdeEI3qr&uhuc=6)W(o%!LP+MCokw~CUXJ_Z(!-su7UnZcr zd#Jv?J{%6i)dvEBGiS~$U%ou8W<^EC)vH&Lt9p8RGQD)}KzhA?YHA9;!0mQxwOZaO z&CSihU=U6*o6U3r81ceqmW1> zg25mFfTrmsOO|kl@t@3KFicNR!x+@*bi6b*H8n1m%WAc*TD1zJX0=*jjjO7v;=*8n zcI?;@jYc7j-EQag(B{pXzkT}#^&1)*GN};+;dDA#mi2nQa=Bb6kkM#F)`y0MGL;1+ zTCFw^2tfVb-rl84muAvbC=@Vdrw!`#Kn%mcjli<3)oSIX*|u#PqVMkRP7T$Wa{Tyl zsCoPLZO%}7prWE8kH>@7x^m@8JdIkdM$JZFUtdW{2}U_PJG-Z+2P*H|w@(mIYilc< z;PrZWeWTawQJgs(4$PN=a_-zYv{clS;DFYzUyqz$Q&WStuGMOP{P=<54F9D706Lux z71z|dksb)S=jP3uc=wl=8da=B=l#?z=&DioBXqoc*e#TaF7aYg;j=g*%p z(;h#5?Dcv(J3C=m;DNSp-ww0iz`y{*Fk@q5c>UiFCE3~k@v+!L-J?g3JRZ-)#6&0* z;tZKgrn0iK3l}b+Cr_R{*}HddFc>^=;DDh1Y33)}y?ZzGZA(iF|IBn^u~_2qIA`Zd zrBZ=+EZk2vIy(CE=T87Yd3pI9dLWHPqf{zIB2iOQ)6D0gP$-c|2(>bEH90vsA3uI% zS$1k_N~KZ>M`>jvhxw_crG-n4@_!ty?ggAO2Go#RwQ=2J&{PHmXR%6wm_R* zU0t&VTCrlq$jAt)$-Kct5Cn>h6DLmGyLS%_m6Vk1+O_NA#fxKOW2me?fByXF(W4B* zq~0cGY|u7s+B7*i3FrHKz9UDDNTt%WG;+DTt*s4hE0IWq0$D1Re)#Z#%`7KRo)iiM z05BSj9*stC-MZD$(V@|3 z)~;QA(oFN91cTTp*b{R^w{Q^KNg00;m8002ov JPDHLkV1knPSBU@s literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/custom_link@3x.png b/Telegram/Resources/icons/settings/premium/features/custom_link@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..72bb62cf9ba7cfc140a87ea031df4866a9cfb6c5 GIT binary patch literal 2455 zcmV;I3263-P)tsZpgq;B}0WN6Cwx}3YL^ugb7%rE(F2i z!Xmg31Q!8|1)-2dARm@21}stp!G(}w!I02Jz>;E1z+j6ZB>{s0L&OlllAsxjGGfAr zZ!x@i^G%GI8EyM`FTa(4X3jn5o^#K?dm!i*x46YEZt;JBfBdP1Q53~6Yz9UL4~ zDwW^Ko=g~x#&9^C&*vG2>D`2UJ|B%nO(qin;7>DtJ*-x1EEX%5%Qwm+z{$x;JRY~( z?SEQ=ez01t*=+VY))|JOD2k$}0czac-F3U&QrG>3XADJAzu)h6yR}-a;IF;Cz5V_D zVzK!1=g(%d$v&)5DDL0CKQlA)=+PsA1y(ARm6er9Br+=3A|Ege2ZO<8vnkNe#>Pe< z5U^M*8jS`)5EzC50000Oh7kl&tJP+++2`|p`0zoX!%n9Yi^UKG`IA!NI3A0|D2fvJ z1H0Xh3zwwoE3&r%=jPo`E^wkwfk_v@F9zg^_0)YUZS~i=t*=(a_`(`F0k%+*jMWa!X=64-13=`;W zX=zE4@`nO~Akk=4AXPXV9?6lwFpPiR%H^`fVi}&SDA?_Gfd$6naSX#oMc-nv@crcS z^73!;kWQzQzi=oN8m+Y;2ojISIr=+0J65Z8g!He4Mx)7Qvz<mL5+ z@8oPq)a&)*<74hCo0MJm@R*DR001_dO{rAoa=B8eB!@@2XfzrZ7Zm?cIXO)f*=Tk!650JSgqDtt;U*StyUYEV-!T;PNyTU3WQ-e5C~*4 znRGfWDL=AWtz5LM)oKI6v;d|ativY+eqI$O*2g;>GZWYp{R^26@h+S>Q;-$hd3;P?A|J|A~H?%usSK0Yq; zT@*z*(`~ogtyW9w>VE@X&tkFgi6jyU`O#Uh&8bvs_;bbbgrcYRM^m@IWPG@;}xvyW*>2!X--|O`b)5Pwhv1YT$Z8iWvYF;M>8jS{q zVZC0DVc69(Tr3ug#iG>1DL|jTV!Peu4_Hi1O^xtw0RW)W>7G4%20;*pVOFd4s#?0; z?vPSJ5Y)#Px7%%kAZBN0*{@?`V_&{}snu#jt^`34f*=lu!{_roefkuFpgVW&+`D%V zhGB}L#QWZ%;B-2<7(&zZ@W%`Qu)4ZhE|&$@?QFQKR;wEu8+|L0`T6Xp!!bNrwOUP*B)`|d z=1Ml3t*?txtJUmd)oOKmdU|*UAP9mi7E85S z_toWcc|4xsZ+^So?&0Af1VP1O@w&l11VQwAJy$6M0L*5y*=!!!^{>n25;PSPiGFJ^Fkk}$ZP~>N` zS(C{mG9y6{gdm7aCd20gK@gQnCD{I}n>YZwySoO1LB3!R1i>&I3WWp(5?jxDJRYOb zh~qdn>%{@bag)j9@pux61ltdso0}WF(89ukp#3-iola+POGngLACBWMU%qs?Tvz*w z+uPf@T<-AjkiBbUHv)#?nVA`b!7w{J%Ngz0uV0@$c`{%XjYcy!H)k*yR4Ucf)Kp)K zI6FH#J3Bi%I{NnQ+ow;TL|#7K0FTErAjr}*O;J>>RvR!NysR3A*`WP%f8Ak`(Vz5d<+Bjr?ib&CShWdmcB!>2xNONq(Pr0FI81 z5{ZP<>6Eb$zfmQr1qjI-odp0)PEO9u%shPfP^;ByG#YmBmGyhAR;$%&H5!fM#djN)=2?hYD)oPVW1pr`^BUY-*%gaioGJI9v z>-8WADi({6A3q*hk3cF|M?YGcsZ^>~tNH!@|B>k;4l0$3Ac#>8s@&ohx46Y1{0qwV V;fE&mh&ccN002ovPDHLkV1k~hsdfMW literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/custom_reactions.png b/Telegram/Resources/icons/settings/premium/features/custom_reactions.png new file mode 100644 index 0000000000000000000000000000000000000000..b551377459a76f8ba679c0107f20e76d3752b5d2 GIT binary patch literal 668 zcmV;N0%QG&P)Tl{;%{K@^2Yi6Ry%iim}e1S~Wl$N?1#CD?h2kis-U zAwi3n!b(BJe;^@ZttdVUv5ep!urQ|yL@WdiC}}K&7(@%nx`kdj=lIB#+||uqdp`Ew z!^{YDI^A?SCBKtQCi^9UCAnOFRK7qU*a;D`*B2KTtyXKfTrQW(*VosZo12YAQB=KN zpUr09-`^h}AF)_$Z$+t83IKrT=jTSFF`LZ*FbrcdnUcxm%4)S*)oOJzne=)+K4b;D zySoEWEEYu~(Z;^Ly~SVUayg|^$=*y+)cyTE2PBb50KC1uZ8vYRSO7deK5kouLID5= zbbWmdz~ypnXA?s7dOa_q)9E^$&Z^S+d>#x2*TZ%=9E1>tVRnLs!yzy7_4Sp_X5Zi6 z2_czGrrYhVt4gQS0K{VPPS8Gu!{K(j&1o$V2mr8Jtw({_T7#5IC4fq$a@1y(N_Ba8 zDHI9``}phiIsmiT{O_P{w+rCppYIojGo6QDbI2^KF4k(w)fu(-GZ!{Wt z6pEq%OePbRO2v+IKn8=s@Ar>JBLMMu{PgscM{zox06srIH5v`i+A*ViKEJv_=jZ3q zXmk_zdc6R~P@00005Qd)LJzY6AdDfU|nREd6E6S z@WPM+V<@yHtzvZ3U0qugB?$@zEl5-{CNWoxv|1v%{=lqFN?WYNTxW>P?djs}_qyNN zzoTovy!d^u&Y$Ob-p_f@`#$gUo+Hs}wKFp_uspV z$^2~wq5cO-r!*tFqN3v9!GoJOZ)RDRVHlq0{eHj0;pp%0Pcb*QZrys|z=5)|GPPQ* zP$;6&XfPObyWKrKJ#M!<4H3t239naHR(5rD&CbqZoFb9P=;)})WXj9S6L^)Cm0iDn zeR6U#-U|_@rl!uHKQEC;Qo$qyYHDiw`SWLNES~2>p^%_~p`oGOyLVGBqtWR1`(q8l z;qZ?iKVqM1Vq$_}l8zIO;}X2Gt5>gDEEaM+IXU_0(W9ZEA+OiVFwB-MTeffCzH8U6 z^73*3*tv73!{Io0?p#Ml$E{np>gwu+2gC8F9H>Q&Uq)rLw4~Xz$*=I-SmH zwML`SWI+G)Qj81+1AUs#&d$QZ!gv?CTz=}*sX!n=E#JL+N004xdqqV>!nQOT4LQ(i zwRLrMD^{#X022#TTwF}wOG`^j>Li|@pWoizE_jI+i$yAxrs@Cy0JwblG9n&1awHXy zL?UTzZ6$GSHk(4BNE;Ub+S=MEO+!P27#9HO>FGhlD_5?h0xB&n4F-dVI6FJLZQC}H zp#i|*a3JEvix)-O7ZenH{rVLVold7prAi89Hk-+Vwzs#7rvU)1R(s~mnY3nW*RD00 zOn2_w`TY4a?bPAn;cM5f)z{aP;qfn^uC6XboSU1gsi|2MkVu_AeL7)sA}TE{1;Mn> z&(8;dKp^1tdb0ua^y$+J7cP{Pl+@JJsMTt6(dYB^_4SR8j*gFy|F$DNB$vzIzkg5q zkWS7lNw3#``}PeHM@B{p3JPLxaUAzMX+|QEa5xMA^YinFm{p*Gfq}unK>+CO?fv-i zqu}lW(A?Y{0H{=|HEY&n6$k)cy?O-zFJ8QezqK%i{C+Ki_SGv@@TrNRU|AQ(nE{;Z{i0Jit1rtaJv~uN2 zkH=0)~yp0 z6aX|D&66ijC{1Hyqe#c}K)JcOWRw5!;ltSLDRQ?)qp{oVi|re+KmhRU*)v4Ebmi5(yyRHQYDs*u#00000NkvXXu0mjf39J5w literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/custom_reactions@3x.png b/Telegram/Resources/icons/settings/premium/features/custom_reactions@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..bd30a9274868e001a59e5acc25219166554fbba0 GIT binary patch literal 2280 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 FV1hQfP4xf( literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/emoji_status@2x.png b/Telegram/Resources/icons/settings/premium/features/emoji_status@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7fc36e7390742c7391bfca10e5a68809bfd41fd9 GIT binary patch literal 1786 zcmVm&6)o3N=XQQC4&2ss zx=s7#d9{7-x#xcFzjN>NJRvbLF*cixEDvKChOk(ye~t0mSS(g(czF1~#`taF;o+hG zn!(!^HD5d)PaqIXPfuGcmZbtB48sIh@%ZuMCX>l(wVKW5=H}-3`1qxP@e^qG?%nzM z`Fr>7#lL6Io`pYcbaYfAk^CP(B9Ta|)lN-K0f5u#tgo*xE-wD{>lbq9bUK5q)z{YpfWzU~x^*idBq}P(Xf$HmlarG>ckZN~PoF+TJuffs zF9O}Ydlvv~Hk(i=L`5c(IUEjbyP=_hdd9`Yxm+#)Fc=I1fnd&P3!VxK165U3VcV6J zl~gP$Dnilaa;2oCP`$ai8TFGVPkIL7a5y3A|iTwd%bppPN!SDc5MJa0)e2b ztII3O*x1;rRjbf}$K&B{cez|UcI==PYierHxum27m6nzk8ewvBlKyULYMMQRK7q>0 z%IH>VYiq-P13+zUE%lMfWZ%Ah1AzYiekw|(QcO`JBO^kgu(-GwD}Ma=F*P+cK0ZD@ zJ$+zc007)>cVc3qUqEa&`_-#g0AR6LcJ10l2vH~$R;v|5t5hoLnVXwCdp6Z-HHwcP zKQ^1q0ARPPXA z^XJczk&&px#>S#2wbIg3>d)nJ^?E%33=a>lUcK5cps=v8=g*(x2*t+6Qe7^WU>=^0=1qHL)of}B0RK9=z zo_4XTtIKn2v$C?Vi#0Vhw2O6hb=}?F6p>*VTs=;w)9YH&)YJq34u``t%g+atnVAW* ztiQkC)9uKSBbb!0T)8qkC?TYG_=W@B2*m`<;Xa<=NC^R$_(~sNje)QFMcSK_eq0ae$ppr%WdEEJ{g9al73BpwVdN?1?wf zx^?StiDX1`Sa&ZCKDdIa}G~kU7c4^ettd{9Xxo@FCd{% z`1R{oPaw3sygbZ}TDHhWMnK7NF);Bj?wG&!2l6Lq*5up)lW=Jc-;eo0Q&Lc z2LN<*bOZ@R2;uYjckbMwDf_~O3qb=(Boe3734dNP0|p|5uvn~1mo8BzwOY;Pa)SU; zC=?i>y1IH%Kxn5=pQcQ0ZEexf(SZV;J9iGN&-0uV01zSMz<~q!K#g0^SGM(KcnAOT z<%>upS{fii$d)ZzFxvuv#bU|K%=EM3#fulQ*6rK3=Wz=dh!B#PnCO`fva+%ktU?cY z0PsAXmj(!{X|-Cqq^hf{=R7OW5)u;V143C@*@BUSe_^H3XeiU`*RP9;iaZZKG?hw4 zA2pvnd$wRqhG9Y&h6(z$uu7$>sHmV%^6%cg>+I|_nM^Y?Gm(*zX=!Qn1y^fp>!Cx3 zCMG5p1PXqH(6(*c_Ta$-%y_=+#*G_u-kkml5L!k?Mnglxr%#_eIqY_OdwY9sZm$1v z7CU8LwsGS|nM@WF6T@b+zkmO3G#Yz*d);n#KtM|~LQ7=~!z{ZO=gSUg*?oLrMMp<3 ctIt#a0Xd8YIh)Qy;{X5v07*qoM6N<$f{k81?EnA( literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/emoji_status@3x.png b/Telegram/Resources/icons/settings/premium/features/emoji_status@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..5e473fd0277d1c14e45bfa9a5ec836a9ae527473 GIT binary patch literal 2551 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 N002ovPDHLkV1ka8&qV+L literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/link.png b/Telegram/Resources/icons/settings/premium/features/link.png new file mode 100644 index 0000000000000000000000000000000000000000..a0c74beac1c99b1735d58cbd59a2f0611d0c7b86 GIT binary patch literal 639 zcmV-_0)YLAP)TmCuWDVHC%o>m5HfTp~htBb0@xStyAj#>j&F3Cf0@ zg@vq?va+1%Zf0dAWvAH50x2`0X%=Hv7G^Olq~!Z9ZjI}@!#mCU-Q9D}^SS4o=iGBC zlgYGNt;p}BP$+&$IOq>e)7$O#;tTKY?vlx*-ELp6*UROS7eLcAp=tU&rBbQjSlw>d zY&QP@Bo>SN{XT$3qY(%MtXAvI%}t?DU~z7@`=3BcrE)wT1Bk_9JeOLnUMv>7-A*o- zUj~gvBLH5nSJ121>jB)~-(Lo$QmJS(`d!t8kWQxqfTAd2kXEbB<#K{^A(cwGkC{vc zfYE4VK_aeCB9RP-!*aPSlgYStu~_`_^78oj$Tf;YM`w>RV!P}2`+1bfWC9=_k8|zq zc3Zf#dc6+7=kxK(nNFtw0)YV6uG8rNFbs3Zh6Ons4gjT6i5q=&bv2vKPAL>c&FAy| zelKwS4-XFjxMdp*hPSsj0Ks68OHn8kgTVlR$KyHb4-0yFdU}6<=N^beBAd-75{Zyw z^?JQi`VnY8pY!UHNF?M$>2x}gNbrmY5W_HES0E^rN|nuKl}e?c_W*LaTmXv2qF|gz zBzk>){Tjb>AVNs9*#vNVdwZG-I|1Qv_`7`hp9s4&YPFir=exeXb~>H;e4bqzyzX8A z5kjifDsPSTdOeH9a)!c!D4M1}KR>xvjYbm+g={w4$Hzyj)p~w@K2IU!+;;r2sQ>jl Z`U{kz7#%2|Yrg;h002ovPDHLkV1lT@=R$ML|(VMIrcX)yBjsX+=Ph)|d*ZX;8sJri<>{YD`>c2&s=)Df6CML#+WI)TqmSn!gO>Qm1 zodNk$&kp9CGryU8=A8fB;Z#*s1p)!=Y8Z~=7?;ca*BF0{%jISW1cHB!@y7%LLB_vk zh_(L(C?g{yD=RCt*t8W;W@e^LCTneNRVtMdi6k#CkD@4#$742|*VfjYPAA($IF4VQ z=Fy`^Mx*iP&!2QWJ2*Ia_wJoqt!4#tNuZ{truq5#xEwJS4u{9b$N79d8i<4Ac*=dK zxw(02YO1ED2K@Z-58xUd92|^|jU`GYl}g{dc>{0NmoHzkv$N9-)YaAH^ZCFDc6WDg-n_{eGB`LG z3WY$(gM3Pka z)2B~Ctl4bl@px$fdiLxY$hf+?T2fMyR4lNsU%!G31VLN`%H;F;XJ=>Z)1920w70iA zolb%v5+9$%#xSh4we|G$6!@D=rt**g;;td>Pj#7KqR1I~)!mH_^A}?%lgyuNUee#+8!G z<n%ATq(x7X=rGGYR+o4LIIu#=&xA@^^MQxLxj!D%#b8$Fc<~~20R`Q8i*hW zaLpErMW@q&v5Keva*t;2$9KT9*@UnvsG4BGCJcp{@0r;yfKVY zaDRWF4Go52o12@rZ{LnYA}mGAUrXk8yMcPFudhcGHKTDjobx~Zet!}Y>im-Nc)ai5 zztc1w3WXF3MQSt}jb?j$`@@G1LZL9(*f@=Ga&i)s(evleQ=m~Qm4}CiAcIb)O9PNh zCIh1*91h2*Cr0b+?1cTsY&NrhUqb<5820w9|dyWMUu7_3$+MNz+h|K{;{VzF4IQt5O$*tCpBz$pQW%Gu_5{T6fT6T8! zojZ5bYW1yKw?raQQBjfC>)qSi+t}DxSy}n^?OR%Cu+(-xcsyQiZf?rEzKlRgZwn{2 rRg!{Txt;tg_wj{WR#tYUK2QAz1l7iN=vl%b00000NkvXXu0mjfRuI)G literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/link@3x.png b/Telegram/Resources/icons/settings/premium/features/link@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..8a01eeeae2c5ab73a7da127ac7d112d49f1f7d69 GIT binary patch literal 1986 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/name@3x.png b/Telegram/Resources/icons/settings/premium/features/name@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..b5d88b1faf622bb54ac578f0230cd4eeab02ade1 GIT binary patch literal 2481 zcmV;i2~PHjP)5uMoK$U z{2s!>{n94=W7IwPd&-}in{#qc&OPVc3qiNsa?35Z+;Yo5$A7we42I#pzCMB=P!vTF z1cqUy1gh8T5Cqj~wPLaO>(?)aVY=(58^w%9qfVzA92~@P9K$d{F$6(4j^iam5QLYn zqhtPY7=|mAN}*6VIXOutle@dShlht(s2GZ(X0zFBHV+OCK6vmz!BLW-TrMY*$sa#{ zL?V&h-CgBhfaAE^?apK}7dJ#W95$QH3TrhmFtED1%ChWDX~mOHr(G@=Z?r9GZHhRK z`~7~0!vS6_6bhT0o2gW)TrO8C75*Y`ocur5>9KIiA0P;#R;vdG2Zx4+K%39ya<5;% z{`BcnJG#k%qG&J}s!GYawziiibyS-Q} z3Y})N*|D)P48xQVyIr(e?ZUzWsN3@LGD(u%_o&zFL!pq+9m}!?gF!igI{-nDxw$#e zYR1RMyYFsnYz#C)kH@2&s2xL5R6HIRgo#F@y}i9%_5}`CG#bTmTv1UwW@KbUkSCwd z+iW(O#{ZSNK~WTF$4g5~<#Jg`F*_%h%WZCM^2Y>0+`D&Arm>`A1VQxo_w&bRXJ@HY zNL?Q%+b-2)Zf^aVjRacy?%6b^y0+}=x<`*9en4!qmpuQJTy(4&1St`-`CgI)6-M0 z*9(Qh{{H^guV0hNq?CqYK<5#SM&)P2YrtqU?(FO|^*8Z&yo2Pr9j|z8I2?|2IxXW% zPft%R7E4=w{X2^3_4<51-*TSF=ks8gF6kBdG8lg>^^aWxG)*roED!`CTuG%;PoF-0 z`0(NL=g&d6Oc2D}++6D(!5rZR|FONjJ#bhek%(5S6?~P!UcSZJv|M!WLcJ`>85h3)#_|E+o)LgAr%Bcg+k#nk4vTGMx)W|^$Mp9!@PX? zQYt@?G;BZY$ zOf;1cMNy~I3C?fvcwD$E={)Y-xg*#~tyXIq?CkFDK6&y)@Wo9XhLG2ZLZ{>7<6JH$ z>QJZC1p)!EZaF(UD;4QG91gG73l3N73UP05ug~WbzCyoMqvP19h3?c(BMVPTvp?c6bfgvS+CdIRvq8zbb>~>v$G>LfhYp66&T}YGMUDS zn&5{*V9k;wY`I%pl$YIS;gTJ)g7 zU|3vS6loO-g*vb!9LGVE2#3RsVN`eU3tS>YYUJa`kD@46tJR7o0G4F~fk4xsQSSAi z5jvetv~8pY7D14f$uG~(pFgcuEBNgA__$~-pJiE>%hl$L`hT_FTCK)gtwy7<*=#gT z*XwmKO zvrQ-Kb{QTX7X7B}!?LW~?FM^WSy=%a#N%;`#e(BFZ@LJAXti38$0G`POQn*i=U6N@ zFfh=)Ry+=eqdUb)r4qQrXt&!zj~9(brQSF)G9t5sBoqpDr-(SZ|MKNaDwVQYt@rQW z@9XP>niVvKeNV#mVzIckwzjmiB=jSs>ktGn4D;&MD=7&!Ha6zx=Nlb81VL}#zJ2rN zO?Qf26_H3p`09Z`K)$hFuNNibrBcacGF^|T9f0FF=!c9(qkMZ5MZphoaBy&Wtu9p> zkV36igD$>h1))-@uq-Q_e)#ZVZEfw6GF*vbj~_o45(0kdK)$_bH5puJ?wU(2BY_|Y zLJ$N+QI8%y8XX-KGWJ+3c6N3q|13$8V48Tz8WE2&icu6bIyy>GRDXXz7@k%tm2cm^ zwS0VNXh<00a=F}C3%MD3dwZn{jKN^AWqzR3>A)>OZKcE4L(_Wj`T2P$6ly18aJgJy z@F7)fy8(_tg0N}1$P|rB3Jr`00000NkvXXu0mjf4_2*& literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/stories.png b/Telegram/Resources/icons/settings/premium/features/stories.png new file mode 100644 index 0000000000000000000000000000000000000000..bac1e2e1456f0ba267b54e09a6606dee8035e830 GIT binary patch literal 806 zcmV+>1KIqEP)Tl|M*&Q546|^NEQ=CW0>tA>vgKX=2&~Gm2<*ik1i( zG=%<8i%SGHG!#gK(GUcML=6Sy+#=K{L}Ucz@?0c}2H#R5_5BT3{ONno8h*d;cJDd& z!@1vg?s=Tk=~NVj{!Oi|tz5I1#bS{pslUJ9X0xT!>4%4hUtm_NRkhRA)pc-ifI7|2 z&JxkZ#l_3ZOC%C87z{cP6=-#Jl^wpkyquYt>FevW*=#HnjYdnQ5`c}3jbA{?WD>yD z)s-lU&2A75hl9c3PoT}sO#oY4TR&UDG&MDKe}CWE*{KE%4-W%4Jv~(&@caD^heHeU z`FsE#A0LfIV*^N*<hoMK7l7^U z?UR!e=Do#YQL)Cy#~Uiz+1UYLGMVa=Mn*;exZQ3dT3J~E@bU36Iy$OrSrwGaV^20Q0}5U@!>4i)DT)Fh91d&G@9yq~LLsVe$YQZrDwR}u^YinHqGYq#?(S|a zWpQy4Kr|X{0F8}}0odQ)*R5PDyWI|;P$<-{#+%LNa=Bb66x!O_mY0_+mCDV{&95My z=UZA@VzJoU+Zz#?&E|$Na(sLY;Oy*75QJukg@uJgBB9#U8Eo-*oITXT!^6qR$@cbk zp66M}<#Nfg3?QG+*Y}q`h=|;7cRHQcj@pTd30aoc*Vk1P25LI|9S%nz5EvXB6a?Yu k=qQ`bHp^t<{@3s54=Qb2NVcx~^#A|>07*qoM6N<$f`l-J`2YX_ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/stories@2x.png b/Telegram/Resources/icons/settings/premium/features/stories@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d077b13485321efddee18415730ef9e409120d66 GIT binary patch literal 1789 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/transcribe@2x.png b/Telegram/Resources/icons/settings/premium/features/transcribe@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..75bbedc2497eda699c81f6494dd4b8eae557a53c GIT binary patch literal 1403 zcmV->1%&#EP)MhX6b0=4B$2;oRV z6j7!LK?M;5jp2llm7rdX)R12gqYHx|a4-|QHbSzSCevXMv=lQF@f=&uHhg{;@6Xd| zC(q{DL@$2dtFz~Qp6C7WzR!98oTX{C+8;lD!0}LacJ@&tNX0PBQ6u~o!!XoQGsNnD zpm?H_30A387K`P|l`95=0RTRI`ZO{!5(oqmiM@OGuGwsEYirxy-u|OOaZ$Imwaw4Z zS{#%^5x6a)D$9mJf5V0 zMn*;uadL7}+T?P%#Pt*ohdGWDej1HN;x;icfr!_xT>}7~=Od8_c_%=@!bsD!#bN<~ zp`jsZ>+bHZOl&k71pvKXPcmG(bjfD3g+d{krf=N1@!Rh}BV;fb5HS*oRE zXU?1vrmV8Ek~sAC_M${q*TZg-e#|yZiI! zPuN?25?34`old75N+O!3_uKyk$pM`|e?Axtj*gC|3TSwEI2a6GxNt!sPCT+0pR4xlj3gvsuL$p zWMpIjKqwRvjHIeKj#CJt=} zpr8N%*4EbKB4yU0yu3WJhKhGCx7&?~Uawak=;h0oi1_5m6Tza8PMfB*h{XJ=<^ZEbCBt=(?-`FzCw{P}aS zKRLG`qPQZJ0Kr~0ym|8`#@^lC&GWqUwDs-VH|fZ|d-o0z$HvCw`$`BXKR=)Jzi0JeA>g(&}of{e&W@l&j zo9om70l?L(SC^KSNH>8%;K73jb#-;Qxw#C(Fbq>rP*7J_=X5$378Zm8Z)j*pJin5Q zBFnOUeSPHeAc*Jrm^Y5+`TqX?L@p=E0ug0qW?HRQx7+>k<3};a;^Ly)?QUslQI=LY zkf5}*G^5c-XiO$kGO1Dn1OOC85g>B=OcE#sXKJ-t9rKWkj@>uL6aWb`mHd=JN`Q8D zcF05Q=SxLO!9|hdxXYI>QxvtewUvCfWB1p>Q6or{cHI7+`WMHXBoU1~#1a4i002ov JPDHLkV1fgKrbYk& literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/features/transcribe@3x.png b/Telegram/Resources/icons/settings/premium/features/transcribe@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..b2c516641f2fdccbaf4dd070c43fc0e933642285 GIT binary patch literal 2400 zcmV-m37__fP) z^)}TCAPxe6@-R{CMPEg1OgWq7XW}-tsWj8Zfa`M>2x;R zjEIORE-vox@4s>5#)3+0HrwV37Dr-Y;?t*3l}aT_Ky!2RQY<$%HbOhQcI~pLz-F`U zLM$jK=|oTyScgj)Ht8d*J`zke;_uSP5n?zN=j;MYzzzx z#B#d3yZie3AQ=va!{u^oYHG}jI5|1h*4Cz{r+0UE=j7y!jEpQ||KP!cSZAcAr3r3Fcs!!cg9i^#6g3)+?d|PYCl?kL;?&q|wga2Z zUW(_%5EK+7lgZ#&;nJl`=g*&CNHaAxH7zZTm_IW!bNKLKE|&{`e){xj$BrHE-@nHc z;^X77>BQslva_?VT)6@Oc>VfyQc{w+DJpxcsHg~jKbcIn$O!cE@){o>U&veH@aWMa z8tvaLL_9VpiT-l6_jPmkwnN0R?79~S5IyxGTiZ5Tj zgolUQW@HiP!DKSuym_R|=)k}L+>TO7Oehp;G#V5|KYsie92`uxIETYoe1X1q?;e!4wzlF#At50~ zqY+AX@7@hFmkTjxXXlod7T8y-SFgrN1p>kJ^fZd12!co?5@PY{>S{==t*s5_EEi(1 zDPpmB)v8rQ(Z!1wv4zCJ!GWl-VZ#P&pe7_F01Dmx(xKDooeVZl#+cjvplLk<0s?{C4uh+9! zEJ|!H3t|X@%+JpQ08Azmf*_RGTo%Oa0dmB6Jf6k#pS=+t9!_?rMuM17C>$CZ8X6kf zym>RphAj!Hsi`9)Bk$h5TeohV^`%GbuMy1GV0M8Ndu=&1E(N$eGUe0<=J4nYvHSd5c~goME80E(jL&YiQq0VhtJz)n+q zJ|8FDx^*j@zzT%|i}U;xq&LCXS=HhR0*eIC&(Ei%q*!AxB_#!8)zHvDRH(17htj5| zChOf2X=1s#xv-k?@$r=_SK_1|9v*{(gHS=I(_sT_A>`-h>vTG(FO^C?Jw0&>&d$!m z!^2P^Cnv}Hc9JIM@9#f3ISFezcI+5YK_Zcuhs4Uu%OfKraZ3}8M&tAOH8nNnnihlJ zM~)nUQk6;-6cl7fVgP`;x;pF|TXYgRIXUJxN%Qmbt*xzvg@pwL1qB5KB_$>I@8374 zG(A1NZ{I$m9d~#4{{DWbQ(aw6s*$!3i;j+lF(VX37rJ>!NJ!}D=s>N&%a6BR-z z6wb}fLFLPrFOzMdjn_yV4yV7r-_Oqv03esklarI-3216tgZQ8UcDk_S@;Q#=p zr>A>*dY(Od*45QDH#cXoRA6A>ix)3KLqh=oGMQ}s`t{$xeSxZ{N<#%ZrVT_44v!v)ODm+sDU8BogK3=2lfz_4W0ce-RW#+uPgY;^LOx z9x5-_>2&(3Q>O+71}v^xI-O3d)oQg`yh(@Cs8yg!K z80hHe=;`UfPN)>ZUa#NG;qLD4=jVqVtLp0N0s;bv2XJa(m)CDNXfzs)MuWv+rlzJW zUV>4MUlhB?4mhO3AxpvEONsv){t}}IO9}Cl!Y?9*os|DH=P@=m1`|@L^w-q*i{5!u zRaGGf;^^pDTU+~UYP9>#> PeerColors::suggestedValue() const { auto PeerColors::indicesValue() const -> rpl::producer { - return rpl::single(_colorIndicesCurrent - ? *_colorIndicesCurrent - : Ui::ColorIndicesCompressed() + return rpl::single( + indicesCurrent() ) | rpl::then(_colorIndicesChanged.events() | rpl::map([=] { - return *_colorIndicesCurrent; + return indicesCurrent(); })); } -int PeerColors::requiredLevelFor(PeerId channel, uint8 index) const { +Ui::ColorIndicesCompressed PeerColors::indicesCurrent() const { + return _colorIndicesCurrent + ? *_colorIndicesCurrent + : Ui::ColorIndicesCompressed(); +} + +const base::flat_map &PeerColors::requiredLevelsGroup() const { + return _requiredLevelsGroup; +} + +const base::flat_map &PeerColors::requiredLevelsChannel() const { + return _requiredLevelsChannel; +} + +int PeerColors::requiredGroupLevelFor(PeerId channel, uint8 index) const { if (Data::DecideColorIndex(channel) == index) { return 0; - } else if (const auto i = _requiredLevels.find(index) - ; i != end(_requiredLevels)) { + } else if (const auto i = _requiredLevelsGroup.find(index) + ; i != end(_requiredLevelsGroup)) { + return i->second; + } + return 1; +} + +int PeerColors::requiredChannelLevelFor(PeerId channel, uint8 index) const { + if (Data::DecideColorIndex(channel) == index) { + return 0; + } else if (const auto i = _requiredLevelsChannel.find(index) + ; i != end(_requiredLevelsChannel)) { return i->second; } return 1; @@ -100,7 +123,8 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) { }; const auto &list = data.vcolors().v; - _requiredLevels.clear(); + _requiredLevelsGroup.clear(); + _requiredLevelsChannel.clear(); suggested.reserve(list.size()); for (const auto &color : list) { const auto &data = color.data(); @@ -110,8 +134,11 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) { continue; } const auto colorIndex = uint8(colorIndexBare); + if (const auto min = data.vgroup_min_level()) { + _requiredLevelsGroup[colorIndex] = min->v; + } if (const auto min = data.vchannel_min_level()) { - _requiredLevels[colorIndex] = min->v; + _requiredLevelsChannel[colorIndex] = min->v; } if (!data.is_hidden()) { suggested.push_back(colorIndex); diff --git a/Telegram/SourceFiles/api/api_peer_colors.h b/Telegram/SourceFiles/api/api_peer_colors.h index 0ad1a63c7..f8d379020 100644 --- a/Telegram/SourceFiles/api/api_peer_colors.h +++ b/Telegram/SourceFiles/api/api_peer_colors.h @@ -25,10 +25,19 @@ public: [[nodiscard]] std::vector suggested() const; [[nodiscard]] rpl::producer> suggestedValue() const; + [[nodiscard]] Ui::ColorIndicesCompressed indicesCurrent() const; [[nodiscard]] auto indicesValue() const -> rpl::producer; - [[nodiscard]] int requiredLevelFor( + [[nodiscard]] auto requiredLevelsGroup() const + -> const base::flat_map &; + [[nodiscard]] auto requiredLevelsChannel() const + -> const base::flat_map &; + + [[nodiscard]] int requiredGroupLevelFor( + PeerId channel, + uint8 index) const; + [[nodiscard]] int requiredChannelLevelFor( PeerId channel, uint8 index) const; @@ -42,7 +51,8 @@ private: mtpRequestId _requestId = 0; base::Timer _timer; rpl::variable> _suggested; - base::flat_map _requiredLevels; + base::flat_map _requiredLevelsGroup; + base::flat_map _requiredLevelsChannel; rpl::event_stream<> _colorIndicesChanged; std::unique_ptr _colorIndicesCurrent; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index 38f064cb2..15e30cba2 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -527,9 +527,13 @@ void Apply( } else { CheckBoostLevel(show, peer, [=](int level) { const auto peerColors = &peer->session().api().peerColors(); - const auto colorRequired = peerColors->requiredLevelFor( - peer->id, - values.colorIndex); + const auto colorRequired = peer->isMegagroup() + ? peerColors->requiredGroupLevelFor( + peer->id, + values.colorIndex) + : peerColors->requiredChannelLevelFor( + peer->id, + values.colorIndex); const auto iconRequired = values.backgroundEmojiId ? session->account().appConfig().get( "channel_bg_icon_level_min", diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp index 178945d32..494996dbb 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp @@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/peers/replace_boost_box.h" +#include "api/api_peer_colors.h" +#include "apiwrap.h" #include "base/event_filter.h" #include "base/unixtime.h" #include "boxes/peer_list_box.h" #include "data/data_channel.h" +#include "data/data_cloud_themes.h" #include "data/data_session.h" #include "lang/lang_keys.h" #include "main/main_account.h" @@ -419,6 +422,47 @@ Ui::BoostCounters ParseBoostCounters( }; } +Ui::BoostFeatures LookupBoostFeatures(not_null channel) { + const auto group = channel->isMegagroup(); + const auto appConfig = &channel->session().account().appConfig(); + const auto get = [&](const QString &key, int fallback, bool ok = true) { + return ok ? appConfig->get(key, fallback) : 0; + }; + + auto nameColorsByLevel = base::flat_map(); + auto linkStylesByLevel = base::flat_map(); + const auto peerColors = &channel->session().api().peerColors(); + const auto &list = group + ? peerColors->requiredLevelsGroup() + : peerColors->requiredLevelsChannel(); + const auto indices = peerColors->indicesCurrent(); + for (const auto &[index, level] : list) { + if (!Ui::ColorPatternIndex(indices, index, false)) { + ++nameColorsByLevel[level]; + } + ++linkStylesByLevel[level]; + } + + return Ui::BoostFeatures{ + .nameColorsByLevel = std::move(nameColorsByLevel), + .linkStylesByLevel = std::move(linkStylesByLevel), + .linkLogoLevel = get(u"channel_bg_icon_level_min"_q, 4, !group), + .transcribeLevel = get(u"group_transcribe_level_min"_q, 6, group), + .emojiPackLevel = get(u"group_emoji_stickers_level_min"_q, 4, group), + .emojiStatusLevel = get(group + ? u"group_emoji_status_level_min"_q + : u"channel_emoji_status_level_min"_q, 8), + .wallpaperLevel = get(group + ? u"group_wallpaper_level_min"_q + : u"channel_wallpaper_level_min"_q, 9), + .wallpapersCount = int( + channel->owner().cloudThemes().chatThemes().size()), + .customWallpaperLevel = get(group + ? u"channel_custom_wallpaper_level_min"_q + : u"group_custom_wallpaper_level_min"_q, 10), + }; +} + int BoostsForGift(not_null session) { const auto key = u"boosts_per_sent_gift"_q; return session->account().appConfig().get(key, 0); diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.h b/Telegram/SourceFiles/boxes/peers/replace_boost_box.h index f15cf0b14..74ab7e093 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.h +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.h @@ -9,12 +9,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/object_ptr.h" +class ChannelData; + namespace Main { class Session; } // namespace Main namespace Ui { struct BoostCounters; +struct BoostFeatures; class BoxContent; class RpWidget; } // namespace Ui @@ -39,6 +42,9 @@ struct ForChannelBoostSlots { [[nodiscard]] Ui::BoostCounters ParseBoostCounters( const MTPpremium_BoostsStatus &status); +[[nodiscard]] Ui::BoostFeatures LookupBoostFeatures( + not_null channel); + [[nodiscard]] int BoostsForGift(not_null session); object_ptr ReassignBoostsBox( diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.cpp b/Telegram/SourceFiles/ui/boxes/boost_box.cpp index dc47020a2..fc374dc30 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/boost_box.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/boxes/boost_box.h" +#include "info/profile/info_profile_icon.h" #include "lang/lang_keys.h" #include "ui/boxes/confirm_box.h" #include "ui/effects/fireworks_animation.h" @@ -14,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" +#include "ui/wrap/fade_wrap.h" #include "ui/painter.h" #include "styles/style_giveaway.h" #include "styles/style_layers.h" @@ -45,10 +47,11 @@ namespace { } [[nodiscard]] object_ptr MakeTitle( - not_null box, + not_null parent, rpl::producer title, - rpl::producer repeated) { - auto result = object_ptr(box); + rpl::producer repeated, + bool centered = true) { + auto result = object_ptr(parent); struct State { not_null title; @@ -57,7 +60,7 @@ namespace { const auto notEmpty = [](const QString &text) { return !text.isEmpty(); }; - const auto state = box->lifetime().make_state(State{ + const auto state = parent->lifetime().make_state(State{ .title = Ui::CreateChild( result.data(), rpl::duplicate(title), @@ -83,7 +86,9 @@ namespace { const auto available = outer - repeated - skip; const auto use = std::min(state->title->textMaxWidth(), available); state->title->resizeToWidth(use); - const auto left = (outer - use - skip - repeated) / 2; + const auto left = centered + ? (outer - use - skip - repeated) / 2 + : 0; state->title->moveToLeft(left, 0); const auto mleft = st::boostTitleBadge.margin.left(); const auto mtop = st::boostTitleBadge.margin.top(); @@ -103,6 +108,171 @@ namespace { return result; } +[[nodiscard]] object_ptr MakeFeaturesBadge( + not_null parent, + rpl::producer text) { + auto result = object_ptr( + parent, + std::move(text), + st::boostLevelBadge); + const auto label = result.data(); + + label->show(); + label->paintRequest() | rpl::start_with_next([=] { + const auto size = label->textMaxWidth(); + const auto rect = QRect( + (label->width() - size) / 2, + st::boostLevelBadge.margin.top(), + size, + st::boostLevelBadge.style.font->height + ).marginsAdded(st::boostLevelBadge.margin); + auto p = QPainter(label); + auto gradient = QLinearGradient( + rect.topLeft(), + rect.topRight()); + gradient.setStops(Ui::Premium::GiftGradientStops()); + p.setBrush(gradient); + p.setPen(Qt::NoPen); + p.drawRoundedRect(rect, rect.height() / 2., rect.height() / 2.); + + const auto &lineFg = st::windowBgRipple; + const auto line = st::boostLevelBadgeLine; + const auto top = st::boostLevelBadge.margin.top() + + ((st::boostLevelBadge.style.font->height - line) / 2); + const auto left = 0; + const auto skip = st::boostLevelBadgeSkip; + if (const auto right = rect.x() - skip; right > left) { + p.fillRect(left, top, right - left, line, lineFg); + } + const auto right = label->width(); + if (const auto left = rect.x() + rect.width() + skip + ; left < right) { + p.fillRect(left, top, right - left, line, lineFg); + } + }, label->lifetime()); + + return result; +} + +void AddFeaturesList( + not_null container, + const Ui::BoostFeatures &features, + int startFromLevel, + bool group) { + const auto add = [&]( + rpl::producer text, + const style::icon &st) { + const auto label = container->add( + object_ptr( + container, + std::move(text), + st::boostFeatureLabel), + st::boostFeaturePadding); + object_ptr( + label, + st, + st::boostFeatureIconPosition); + }; + const auto proj = &Ui::Text::RichLangValue; + const auto max = std::max({ + features.linkLogoLevel, + features.transcribeLevel, + features.emojiPackLevel, + features.emojiStatusLevel, + features.wallpaperLevel, + features.customWallpaperLevel, + (features.nameColorsByLevel.empty() + ? 0 + : features.nameColorsByLevel.back().first), + (features.linkStylesByLevel.empty() + ? 0 + : features.linkStylesByLevel.back().first), + }); + auto nameColors = 0; + auto linkStyles = 0; + for (auto i = std::max(startFromLevel, 1); i <= max; ++i) { + const auto unlocks = (i == startFromLevel); + container->add( + MakeFeaturesBadge( + container, + (unlocks + ? tr::lng_boost_level_unlocks + : tr::lng_boost_level)( + lt_count, + rpl::single(float64(i)))), + st::boostLevelBadgePadding); + add( + tr::lng_feature_stories(lt_count, rpl::single(float64(i)), proj), + st::boostFeatureStories); + if (!group) { + add(tr::lng_feature_reactions( + lt_count, + rpl::single(float64(i)), + proj + ), st::boostFeatureCustomReactions); + if (const auto j = features.nameColorsByLevel.find(i) + ; j != end(features.nameColorsByLevel)) { + nameColors += j->second; + } + if (nameColors > 0) { + add(tr::lng_feature_name_color_channel( + lt_count, + rpl::single(float64(nameColors)), + proj + ), st::boostFeatureName); + } + if (const auto j = features.linkStylesByLevel.find(i) + ; j != end(features.linkStylesByLevel)) { + linkStyles += j->second; + } + if (linkStyles > 0) { + add(tr::lng_feature_link_style_channel( + lt_count, + rpl::single(float64(linkStyles)), + proj + ), st::boostFeatureLink); + } + if (i >= features.linkLogoLevel) { + add( + tr::lng_feature_link_emoji(proj), + st::boostFeatureCustomLink); + } + } + if (group && i >= features.emojiPackLevel) { + add( + tr::lng_feature_custom_emoji_pack(proj), + st::boostFeatureCustomEmoji); + } + if (group && i >= features.transcribeLevel) { + add( + tr::lng_feature_transcribe(proj), + st::boostFeatureTranscribe); + } + if (i >= features.emojiStatusLevel) { + add( + tr::lng_feature_emoji_status(proj), + st::boostFeatureEmojiStatus); + } + if (i >= features.wallpaperLevel) { + add( + (group + ? tr::lng_feature_backgrounds_group + : tr::lng_feature_backgrounds_channel)( + lt_count, + rpl::single(float64(features.wallpapersCount)), + proj), + st::boostFeatureBackground); + } + if (i >= features.customWallpaperLevel) { + add( + (group + ? tr::lng_feature_custom_background_group + : tr::lng_feature_custom_background_channel)(proj), + st::boostFeatureCustomBackground); + } + } +} + } // namespace void StartFireworks(not_null parent) { @@ -153,7 +323,10 @@ void BoostBox( state->data.value(), st::boxRowPadding); - box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); + box->setMaxHeight(st::boostBoxMaxHeight); + const auto close = box->addTopButton( + st::boxTitleClose, + [=] { box->closeBox(); }); const auto name = data.name; @@ -192,21 +365,12 @@ void BoostBox( Ui::Text::RichLangValue); return (counters.mine || full) ? (left - ? (!counters.level - ? (data.group - ? tr::lng_boost_channel_you_first_group - : tr::lng_boost_channel_you_first)( - lt_count, - rpl::single(float64(left)), - Ui::Text::RichLangValue) - : (data.group - ? tr::lng_boost_channel_you_more_group - : tr::lng_boost_channel_you_more)( - lt_count, - rpl::single(float64(left)), - lt_post, - std::move(post), - Ui::Text::RichLangValue)) + ? tr::lng_boost_channel_needs_unlock( + lt_count, + rpl::single(float64(left)), + lt_channel, + rpl::single(bold), + Ui::Text::RichLangValue) : (!counters.level ? (data.group ? tr::lng_boost_channel_reached_first_group @@ -220,23 +384,38 @@ void BoostBox( lt_post, std::move(post), Ui::Text::RichLangValue))) - : !counters.level - ? tr::lng_boost_channel_needs_first( + : tr::lng_boost_channel_needs_unlock( lt_count, rpl::single(float64(left)), lt_channel, rpl::single(bold), - Ui::Text::RichLangValue) - : tr::lng_boost_channel_needs_more( - lt_count, - rpl::single(float64(left)), - lt_channel, - rpl::single(bold), - lt_post, - std::move(post), Ui::Text::RichLangValue); }) | rpl::flatten_latest(); + auto faded = object_ptr>( + close->parentWidget(), + MakeTitle( + box, + rpl::duplicate(title), + rpl::duplicate(repeated), + false)); + const auto titleInner = faded.data(); + titleInner->move(st::boxTitlePosition); + titleInner->resizeToWidth(st::boxWideWidth + - st::boxTitleClose.width + - st::boxTitlePosition.x()); + titleInner->hide(anim::type::instant); + crl::on_main(titleInner, [=] { + titleInner->raise(); + titleInner->toggleOn(rpl::single( + rpl::empty + ) | rpl::then( + box->scrolls() + ) | rpl::map([=] { + return box->scrollTop() > 0; + })); + }); + box->addRow( MakeTitle(box, std::move(title), std::move(repeated)), st::boxRowPadding + QMargins(0, st::boostTitleSkip, 0, 0)); @@ -249,6 +428,14 @@ void BoostBox( (st::boxRowPadding + QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip))); + const auto current = state->data.current(); + box->setTitle(rpl::single(QString())); + AddFeaturesList( + box->verticalLayout(), + data.features, + current.level + (current.nextLevelBoosts ? 1 : 0), + data.group); + const auto allowMulti = data.allowMulti; auto submit = state->data.value( ) | rpl::map([=](BoostCounters counters) { @@ -579,7 +766,7 @@ void FillBoostLimit( container->add(object_ptr(container, skip)); }; - addSkip(st::boostSkipTop); + //addSkip(st::boostSkipTop); const auto ratio = [=](BoostCounters counters) { const auto min = counters.thisLevelBoosts; diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.h b/Telegram/SourceFiles/ui/boxes/boost_box.h index 9acc046f7..064e697ef 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.h +++ b/Telegram/SourceFiles/ui/boxes/boost_box.h @@ -30,9 +30,22 @@ struct BoostCounters { BoostCounters) = default; }; +struct BoostFeatures { + base::flat_map nameColorsByLevel; + base::flat_map linkStylesByLevel; + int linkLogoLevel = 0; + int transcribeLevel = 0; + int emojiPackLevel = 0; + int emojiStatusLevel = 0; + int wallpaperLevel = 0; + int wallpapersCount = 0; + int customWallpaperLevel = 0; +}; + struct BoostBoxData { QString name; BoostCounters boost; + BoostFeatures features; bool allowMulti = false; bool group = false; }; diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index 4134c5be9..5cce045bd 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -290,7 +290,7 @@ boostReassignText: FlatLabel(defaultFlatLabel) { } boostBottomSkip: 6px; boostBox: Box(premiumPreviewDoubledLimitsBox) { - buttonPadding: margins(22px, 22px, 22px, 22px); + buttonPadding: margins(16px, 12px, 16px, 12px); buttonHeight: 42px; button: RoundButton(defaultActiveButton) { height: 42px; @@ -337,3 +337,30 @@ showOrBox: Box(boostBox) { buttonPadding: margins(28px, 16px, 28px, 27px); button: showOrShowButton; } + +boostBoxMaxHeight: 512px; +boostLevelBadge: FlatLabel(defaultFlatLabel) { + margin: margins(12px, 4px, 12px, 4px); + style: semiboldTextStyle; + textFg: premiumButtonFg; + align: align(top); +} +boostLevelBadgePadding: margins(30px, 12px, 32px, 12px); +boostLevelBadgeSkip: 8px; +boostLevelBadgeLine: 1px; + +boostFeatureLabel: FlatLabel(defaultFlatLabel) { + margin: margins(36px, 4px, 0px, 4px); +} +boostFeaturePadding: margins(64px, 6px, 24px, 6px); +boostFeatureIconPosition: point(0px, 0px); +boostFeatureBackground: icon{{ "settings/premium/features/background", windowBgActive }}; +boostFeatureCustomBackground: icon{{ "settings/premium/features/custom_background", windowBgActive }}; +boostFeatureCustomEmoji: icon{{ "settings/premium/features/custom_emoji", windowBgActive }}; +boostFeatureCustomLink: icon{{ "settings/premium/features/custom_link", windowBgActive }}; +boostFeatureCustomReactions: icon{{ "settings/premium/features/custom_reactions", windowBgActive }}; +boostFeatureEmojiStatus: icon{{ "settings/premium/features/emoji_status", windowBgActive }}; +boostFeatureLink: icon{{ "settings/premium/features/link", windowBgActive }}; +boostFeatureName: icon{{ "settings/premium/features/name", windowBgActive }}; +boostFeatureStories: icon{{ "settings/premium/features/stories", windowBgActive }}; +boostFeatureTranscribe: icon{{ "settings/premium/features/transcribe", windowBgActive }}; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 258da20c6..8b099aa15 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -630,6 +630,7 @@ void SessionNavigation::resolveBoostState(not_null channel) { uiShow()->show(Box(Ui::BoostBox, Ui::BoostBoxData{ .name = channel->name(), .boost = ParseBoostCounters(result), + .features = LookupBoostFeatures(channel), .allowMulti = (BoostsForGift(_session) > 0), .group = channel->isMegagroup(), }, submit)); From 08efa73b2bf48158316925d14922dd3d1b2f5308 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 8 Feb 2024 21:58:16 +0400 Subject: [PATCH 28/73] Allow editing group wallpaper / status / emoji set. --- Telegram/Resources/langs/lang.strings | 18 ++ Telegram/SourceFiles/apiwrap.cpp | 13 ++ Telegram/SourceFiles/apiwrap.h | 3 + .../boxes/background_preview_box.cpp | 2 +- .../boxes/peers/edit_peer_color_box.cpp | 170 +++++++++++------- .../boxes/peers/edit_peer_info_box.cpp | 7 +- Telegram/SourceFiles/boxes/stickers_box.cpp | 128 ++++++++++--- Telegram/SourceFiles/boxes/stickers_box.h | 3 +- .../chat_helpers/stickers_list_widget.cpp | 6 +- Telegram/SourceFiles/data/data_changes.h | 11 +- Telegram/SourceFiles/data/data_channel.cpp | 28 ++- Telegram/SourceFiles/data/data_channel.h | 1 + Telegram/SourceFiles/ui/boxes/boost_box.cpp | 21 ++- Telegram/SourceFiles/ui/boxes/boost_box.h | 7 + 14 files changed, 308 insertions(+), 110 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 655a29f76..29f186057 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1986,6 +1986,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_stickers" = "Group stickers"; "lng_group_stickers_description" = "You can choose a sticker set which will be available for every member while in the group chat."; "lng_group_stickers_add" = "Choose sticker set"; +"lng_group_emoji" = "Group emoji pack"; +"lng_group_emoji_description" = "Choose an emoji pack that will be available to all members within the group."; "lng_premium" = "Premium"; "lng_premium_free" = "Free"; @@ -2230,15 +2232,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boost_channel_title_wallpaper" = "Enable wallpapers"; "lng_boost_channel_needs_level_wallpaper#one" = "Your channel needs to reach **Level {count}** to change channel wallpaper."; "lng_boost_channel_needs_level_wallpaper#other" = "Your channel needs to reach **Level {count}** to change channel wallpaper."; +"lng_boost_group_needs_level_wallpaper#one" = "Your group needs to reach **Level {count}** to change group wallpaper."; +"lng_boost_group_needs_level_wallpaper#other" = "Your group needs to reach **Level {count}** to change group wallpaper."; "lng_boost_channel_title_status" = "Enable emoji status"; "lng_boost_channel_needs_level_status#one" = "Your channel needs to reach **Level {count}** to set emoji status."; "lng_boost_channel_needs_level_status#other" = "Your channel needs to reach **Level {count}** to set emoji status."; +"lng_boost_group_needs_level_status#one" = "Your group needs to reach **Level {count}** to set emoji status."; +"lng_boost_group_needs_level_status#other" = "Your group needs to reach **Level {count}** to set emoji status."; "lng_boost_channel_title_reactions" = "Custom reactions"; "lng_boost_channel_needs_level_reactions#one" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as a reaction."; "lng_boost_channel_needs_level_reactions#other" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as reactions."; +"lng_boost_group_title_emoji" = "Enable emoji pack"; +"lng_boost_group_needs_level_emoji#one" = "Your group needs to reach **Level {count}** to set emoji pack."; +"lng_boost_group_needs_level_emoji#other" = "Your group needs to reach **Level {count}** to set emoji pack."; + "lng_boost_channel_ask" = "Ask your **Premium** subscribers to boost your channel with this link:"; "lng_boost_channel_ask_button" = "Copy Link"; "lng_boost_channel_or" = "or"; @@ -2518,6 +2528,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stickers_remove_pack_confirm" = "Remove"; "lng_stickers_archive_pack" = "Archive Stickers"; "lng_stickers_has_been_archived" = "Sticker pack has been archived."; +"lng_emoji_group_set" = "Group emoji set"; +"lng_emoji_remove_group_set" = "Remove group emoji set?"; +"lng_emoji_group_from_your" = "Choose from your emoji"; +"lng_emoji_group_from_featured" = "Choose from trending emoji"; "lng_masks_archive_pack" = "Archive Masks"; "lng_masks_has_been_archived" = "Mask pack has been archived."; "lng_masks_installed" = "Mask pack has been installed."; @@ -3028,8 +3042,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_edit_channel_level_min" = "Level 1+"; "lng_edit_channel_wallpaper" = "Channel wallpaper"; "lng_edit_channel_wallpaper_about" = "Set a wallpaper that will be visible for everyone reading your channel."; +"lng_edit_channel_wallpaper_group" = "Group wallpaper"; +"lng_edit_channel_wallpaper_about_group" = "Set a wallpaper that will be visible for everyone participating in your group."; "lng_edit_channel_status" = "Channel emoji status"; "lng_edit_channel_status_about" = "Choose a status that will be shown next to the channel's name."; +"lng_edit_channel_status_group" = "Group emoji status"; +"lng_edit_channel_status_about_group" = "Choose a status that will be shown next to the group's name."; "lng_edit_self_title" = "Edit your name"; "lng_confirm_contact_data" = "New Contact"; "lng_add_contact" = "Create"; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 73026cc88..c9141d065 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -2606,6 +2606,19 @@ void ApiWrap::setGroupStickerSet( _session->data().stickers().notifyUpdated(Data::StickersType::Stickers); } +void ApiWrap::setGroupEmojiSet( + not_null megagroup, + const StickerSetIdentifier &set) { + Expects(megagroup->mgInfo != nullptr); + + megagroup->mgInfo->emojiSet = set; + request(MTPchannels_SetEmojiStickers( + megagroup->inputChannel, + Data::InputStickerSet(set) + )).send(); + _session->data().stickers().notifyUpdated(Data::StickersType::Emoji); +} + std::vector> *ApiWrap::stickersByEmoji( const QString &key) { const auto it = _stickersByEmoji.find(key); diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 192e14f28..615960126 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -246,6 +246,9 @@ public: void setGroupStickerSet( not_null megagroup, const StickerSetIdentifier &set); + void setGroupEmojiSet( + not_null megagroup, + const StickerSetIdentifier &set); [[nodiscard]] std::vector> *stickersByEmoji( const QString &key); diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index db72d5690..cae929641 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -708,7 +708,7 @@ void BackgroundPreviewBox::checkLevelForChannel() { return std::optional(); } return std::make_optional(Ui::AskBoostReason{ - Ui::AskBoostWallpaper{ required } + Ui::AskBoostWallpaper{ required, _forPeer->isMegagroup()} }); }, [=] { _forPeerLevelCheck = false; }); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index 15e30cba2..0ab69bf3b 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "boxes/peers/replace_boost_box.h" #include "boxes/background_box.h" +#include "boxes/stickers_box.h" #include "chat_helpers/compose/compose_show.h" #include "data/data_changes.h" #include "data/data_channel.h" @@ -557,7 +558,10 @@ void Apply( } const auto reason = [&]() -> Ui::AskBoostReason { if (level < statusRequired) { - return { Ui::AskBoostEmojiStatus{ statusRequired } }; + return { Ui::AskBoostEmojiStatus{ + statusRequired, + peer->isMegagroup() + } }; } else if (level < iconRequired) { return { Ui::AskBoostChannelColor{ iconRequired } }; } @@ -795,7 +799,8 @@ int ColorSelector::resizeGetHeight(int newWidth) { not_null parent, std::shared_ptr show, rpl::producer statusIdValue, - Fn statusIdChosen) { + Fn statusIdChosen, + bool group) { const auto &basicSt = st::settingsButtonNoIcon; const auto ratio = style::DevicePixelRatio(); const auto added = st::normalFont->spacew; @@ -810,7 +815,9 @@ int ColorSelector::resizeGetHeight(int newWidth) { st->padding.setRight(rightPadding); auto result = object_ptr( parent, - tr::lng_edit_channel_status(), + (group + ? tr::lng_edit_channel_status_group() + : tr::lng_edit_channel_status()), *st); const auto raw = result.data(); @@ -901,7 +908,12 @@ void EditPeerColorBox( not_null peer, std::shared_ptr style, std::shared_ptr theme) { - box->setTitle(tr::lng_settings_color_title()); + const auto group = peer->isMegagroup(); + const auto container = box->verticalLayout(); + + box->setTitle(peer->isSelf() + ? tr::lng_settings_color_title() + : tr::lng_edit_channel_color()); box->setWidth(st::boxWideWidth); struct State { @@ -918,52 +930,55 @@ void EditPeerColorBox( state->emojiId = peer->backgroundEmojiId(); state->statusId = peer->emojiStatusId(); - box->addRow(object_ptr( - box, - style, - theme, - peer, - state->index.value(), - state->emojiId.value() - ), {}); - - auto indices = peer->session().api().peerColors().suggestedValue(); - const auto margin = st::settingsColorRadioMargin; - const auto skip = st::settingsColorRadioSkip; - box->addRow( - object_ptr( + if (!group) { + box->addRow(object_ptr( box, style, - std::move(indices), - state->index.current(), - [=](uint8 index) { state->index = index; }), - { margin, skip, margin, skip }); + theme, + peer, + state->index.value(), + state->emojiId.value() + ), {}); - const auto container = box->verticalLayout(); - Ui::AddDividerText(container, peer->isSelf() - ? tr::lng_settings_color_about() - : tr::lng_settings_color_about_channel()); + auto indices = peer->session().api().peerColors().suggestedValue(); + const auto margin = st::settingsColorRadioMargin; + const auto skip = st::settingsColorRadioSkip; + box->addRow( + object_ptr( + box, + style, + std::move(indices), + state->index.current(), + [=](uint8 index) { state->index = index; }), + { margin, skip, margin, skip }); - Ui::AddSkip(container, st::settingsColorSampleSkip); + Ui::AddDividerText(container, peer->isSelf() + ? tr::lng_settings_color_about() + : tr::lng_settings_color_about_channel()); - container->add(CreateEmojiIconButton( - container, - show, - style, - state->index.value(), - state->emojiId.value(), - [=](DocumentId id) { state->emojiId = id; })); + Ui::AddSkip(container, st::settingsColorSampleSkip); - Ui::AddSkip(container, st::settingsColorSampleSkip); - Ui::AddDividerText(container, peer->isSelf() - ? tr::lng_settings_color_emoji_about() - : tr::lng_settings_color_emoji_about_channel()); + container->add(CreateEmojiIconButton( + container, + show, + style, + state->index.value(), + state->emojiId.value(), + [=](DocumentId id) { state->emojiId = id; })); + + Ui::AddSkip(container, st::settingsColorSampleSkip); + Ui::AddDividerText(container, peer->isSelf() + ? tr::lng_settings_color_emoji_about() + : tr::lng_settings_color_emoji_about_channel()); + } if (const auto channel = peer->asChannel()) { Ui::AddSkip(container, st::settingsColorSampleSkip); container->add(object_ptr( container, - tr::lng_edit_channel_wallpaper(), + (group + ? tr::lng_edit_channel_wallpaper_group() + : tr::lng_edit_channel_wallpaper()), st::settingsButtonNoIcon) )->setClickedCallback([=] { const auto usage = ChatHelpers::WindowUsage::PremiumPromo; @@ -973,9 +988,25 @@ void EditPeerColorBox( }); Ui::AddSkip(container, st::settingsColorSampleSkip); - Ui::AddDividerText( - container, - tr::lng_edit_channel_wallpaper_about()); + Ui::AddDividerText(container, group + ? tr::lng_edit_channel_wallpaper_about_group() + : tr::lng_edit_channel_wallpaper_about()); + + if (group) { + Ui::AddSkip(container, st::settingsColorSampleSkip); + + container->add(object_ptr( + container, + tr::lng_group_emoji(), + st::settingsButtonNoIcon) + )->setClickedCallback([=] { + const auto isEmoji = true; + show->showBox(Box(show, channel, isEmoji)); + }); + + Ui::AddSkip(container, st::settingsColorSampleSkip); + Ui::AddDividerText(container, tr::lng_group_emoji_description()); + } // Preload exceptions list. const auto peerPhoto = &channel->session().api().peerPhoto(); @@ -996,10 +1027,13 @@ void EditPeerColorBox( state->statusId = id; state->statusUntil = until; state->statusChanged = true; - })); + }, + group)); Ui::AddSkip(container, st::settingsColorSampleSkip); - Ui::AddDividerText(container, tr::lng_edit_channel_status_about()); + Ui::AddDividerText(container, group + ? tr::lng_edit_channel_status_about_group() + : tr::lng_edit_channel_status_about()); } box->addButton(tr::lng_settings_apply(), [=] { @@ -1024,19 +1058,11 @@ void EditPeerColorBox( }); } -void AddPeerColorButton( - not_null container, - std::shared_ptr show, - not_null peer) { - auto label = peer->isSelf() - ? tr::lng_settings_theme_name_color() - : tr::lng_edit_channel_color(); - const auto button = AddButtonWithIcon( - container, - rpl::duplicate(label), - st::settingsColorButton, - { &st::menuIconChangeColors }); - +void SetupPeerColorSample( + not_null button, + not_null peer, + rpl::producer label, + std::shared_ptr style) { auto colorIndexValue = peer->session().changes().peerFlagsValue( peer, Data::PeerUpdate::Flag::Color @@ -1045,12 +1071,6 @@ void AddPeerColorButton( }); const auto name = peer->shortName(); - const auto style = std::make_shared( - peer->session().colorIndicesValue()); - const auto theme = std::shared_ptr( - Window::Theme::DefaultChatThemeOn(button->lifetime())); - style->apply(theme.get()); - const auto sample = Ui::CreateChild( button.get(), style, @@ -1102,6 +1122,30 @@ void AddPeerColorButton( }, sample->lifetime()); sample->setAttribute(Qt::WA_TransparentForMouseEvents); +} + +void AddPeerColorButton( + not_null container, + std::shared_ptr show, + not_null peer) { + auto label = peer->isSelf() + ? tr::lng_settings_theme_name_color() + : tr::lng_edit_channel_color(); + const auto button = AddButtonWithIcon( + container, + rpl::duplicate(label), + st::settingsColorButton, + { &st::menuIconChangeColors }); + + const auto style = std::make_shared( + peer->session().colorIndicesValue()); + const auto theme = std::shared_ptr( + Window::Theme::DefaultChatThemeOn(button->lifetime())); + style->apply(theme.get()); + + if (!peer->isMegagroup()) { + SetupPeerColorSample(button, peer, rpl::duplicate(label), style); + } button->setClickedCallback([=] { show->show(Box(EditPeerColorBox, show, peer, style, theme)); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 8ae5907e6..7b9abb4c1 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -631,8 +631,9 @@ object_ptr Controller::createStickersEdit() { tr::lng_group_stickers_add(), rpl::single(QString()), //Empty count. [=, controller = _navigation->parentController()] { + const auto isEmoji = false; controller->show( - Box(controller->uiShow(), channel)); + Box(controller->uiShow(), channel, isEmoji)); }, { &st::menuIconStickers }); @@ -1094,8 +1095,8 @@ void Controller::fillManageSection() { const auto canEditStickers = isChannel && channel->canEditStickers(); const auto canDeleteChannel = isChannel && channel->canDelete(); const auto canEditColorIndex = isChannel - && !channel->isMegagroup() - && channel->canEditInformation(); + && (channel->amCreator() + || (channel->adminRights() & ChatAdminRight::ChangeInfo)); const auto canViewOrEditLinkedChat = isChannel && (channel->linkedChat() || (channel->isBroadcast() && channel->canEditInformation())); diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index 3f35e9591..2426906e5 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -17,7 +17,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "mainwidget.h" #include "mainwindow.h" +#include "ui/boxes/boost_box.h" #include "ui/boxes/confirm_box.h" +#include "boxes/peers/edit_peer_color_box.h" #include "boxes/sticker_set_box.h" #include "apiwrap.h" #include "storage/storage_account.h" @@ -38,6 +40,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/painter.h" #include "ui/unread_badge_paint.h" #include "media/clip/media_clip_reader.h" +#include "main/main_account.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" @@ -99,7 +103,8 @@ public: Inner( QWidget *parent, std::shared_ptr show, - not_null megagroup); + not_null megagroup, + bool isEmoji); [[nodiscard]] Main::Session &session() const; @@ -108,7 +113,7 @@ public: } void setInnerFocus(); - void saveGroupSet(); + void saveGroupSet(Fn done); void rebuild(bool masks); void updateSize(int newWidth = 0); @@ -221,6 +226,7 @@ private: StickersSetsOrder collectSets(Check check) const; void updateSelected(); + void checkGroupLevel(Fn done); void checkLoadMore(); void updateScrollbarWidth(); @@ -323,6 +329,8 @@ private: int _scrollbar = 0; ChannelData *_megagroupSet = nullptr; + bool _megagroupSetEmoji = false; + bool _checkingGroupLevel = false; StickerSetIdentifier _megagroupSetInput; std::unique_ptr _megagroupSelectedSet; object_ptr _megagroupSetField = { nullptr }; @@ -429,15 +437,16 @@ StickersBox::StickersBox( StickersBox::StickersBox( QWidget*, std::shared_ptr show, - not_null megagroup) + not_null megagroup, + bool isEmoji) : _st(st::stickersRowItem) , _show(std::move(show)) , _session(&_show->session()) , _api(&_session->mtp()) , _section(Section::Installed) , _isMasks(false) -, _isEmoji(false) -, _installed(0, this, _show, megagroup) +, _isEmoji(isEmoji) +, _installed(0, this, _show, megagroup, isEmoji) , _megagroupSet(megagroup) { _installed.widget()->scrollsToY( ) | rpl::start_with_next([=](int y) { @@ -581,7 +590,9 @@ void StickersBox::prepare() { session().local().readArchivedStickers(); } } else { - setTitle(tr::lng_stickers_group_set()); + setTitle(_isEmoji + ? tr::lng_emoji_group_set() + : tr::lng_stickers_group_set()); } } else if (_section == Section::Archived) { requestArchivedSets(); @@ -659,9 +670,11 @@ void StickersBox::prepare() { } if (_megagroupSet) { - addButton( - tr::lng_settings_save(), - [=] { _installed.widget()->saveGroupSet(); closeBox(); }); + addButton(tr::lng_settings_save(), [=] { + _installed.widget()->saveGroupSet(crl::guard(this, [=] { + closeBox(); + })); + }); addButton(tr::lng_cancel(), [=] { closeBox(); }); } else { const auto close = _section == Section::Attached; @@ -1220,7 +1233,8 @@ StickersBox::Inner::Inner( StickersBox::Inner::Inner( QWidget *parent, std::shared_ptr show, - not_null megagroup) + not_null megagroup, + bool isEmoji) : RpWidget(parent) , _st(st::stickersRowItem) , _show(std::move(show)) @@ -1248,19 +1262,30 @@ StickersBox::Inner::Inner( }) , _itemsTop(st::lineWidth) , _megagroupSet(megagroup) -, _megagroupSetInput(_megagroupSet->mgInfo->stickerSet) +, _megagroupSetEmoji(isEmoji) +, _megagroupSetInput(isEmoji + ? _megagroupSet->mgInfo->emojiSet + : _megagroupSet->mgInfo->stickerSet) , _megagroupSetField( this, st::groupStickersField, - rpl::single(u"stickerset"_q), + rpl::single(isEmoji ? u"emojipack"_q : u"stickerset"_q), QString(), _session->createInternalLink(QString())) , _megagroupDivider(this) -, _megagroupSubTitle(this, tr::lng_stickers_group_from_your(tr::now), st::boxTitle) { +, _megagroupSubTitle( + this, + (isEmoji + ? tr::lng_emoji_group_from_your + : tr::lng_stickers_group_from_your)(tr::now), + st::boxTitle) { _megagroupSetField->setLinkPlaceholder( - _session->createInternalLink(u"addstickers/"_q)); + _session->createInternalLink( + isEmoji ? u"addemoji/"_q : u"addstickers/"_q)); _megagroupSetField->setPlaceholderHidden(false); - _megagroupSetAddressChangedTimer.setCallback([this] { handleMegagroupSetAddressChange(); }); + _megagroupSetAddressChangedTimer.setCallback([this] { + handleMegagroupSetAddressChange(); + }); connect( _megagroupSetField, &Ui::MaskedInputField::changed, @@ -1689,7 +1714,9 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null row, int ind } void StickersBox::Inner::mousePressEvent(QMouseEvent *e) { - if (_dragging >= 0) mouseReleaseEvent(e); + if (_dragging >= 0) { + mouseReleaseEvent(e); + } _mouse = e->globalPos(); updateSelected(); @@ -1979,18 +2006,63 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) { setActionDown(-1); } -void StickersBox::Inner::saveGroupSet() { +void StickersBox::Inner::saveGroupSet(Fn done) { Expects(_megagroupSet != nullptr); - auto oldId = _megagroupSet->mgInfo->stickerSet.id; + auto oldId = _megagroupSetEmoji + ? _megagroupSet->mgInfo->emojiSet.id + : _megagroupSet->mgInfo->stickerSet.id; auto newId = _megagroupSetInput.id; - if (newId != oldId) { + if (newId == oldId) { + done(); + } else if (_megagroupSetEmoji) { + checkGroupLevel(done); + } else { session().api().setGroupStickerSet(_megagroupSet, _megagroupSetInput); session().data().stickers().notifyStickerSetInstalled( Data::Stickers::MegagroupSetId); } } +void StickersBox::Inner::checkGroupLevel(Fn done) { + Expects(_megagroupSet != nullptr); + Expects(_megagroupSetEmoji); + + const auto peer = _megagroupSet; + const auto save = [=] { + session().api().setGroupEmojiSet(peer, _megagroupSetInput); + session().data().stickers().notifyEmojiSetInstalled( + Data::Stickers::MegagroupSetId); + done(); + }; + + if (!_megagroupSetInput) { + save(); + } else if (_checkingGroupLevel) { + return; + } + _checkingGroupLevel = true; + + const auto weak = Ui::MakeWeak(this); + CheckBoostLevel(_show, peer, [=](int level) { + if (!weak) { + return std::optional(); + } + _checkingGroupLevel = false; + const auto appConfig = &peer->session().account().appConfig(); + const auto required = appConfig->get( + "group_emoji_stickers_level_min", + 4); + if (level >= required) { + save(); + return std::optional(); + } + return std::make_optional(Ui::AskBoostReason{ + Ui::AskBoostEmojiPack{ required } + }); + }, [=] { _checkingGroupLevel = false; }); +} + void StickersBox::Inner::setRowRemovedBySetId(uint64 setId, bool removed) { const auto index = getRowIndex(setId); if (index >= 0) { @@ -2233,9 +2305,13 @@ void StickersBox::Inner::rebuild(bool masks) { clear(); const auto &order = ([&]() -> const StickersSetsOrder & { if (_section == Section::Installed) { - auto &result = session().data().stickers().setsOrder(); + auto &result = _megagroupSetEmoji + ? session().data().stickers().emojiSetsOrder() + : session().data().stickers().setsOrder(); if (_megagroupSet && result.empty()) { - return session().data().stickers().featuredSetsOrder(); + return _megagroupSetEmoji + ? session().data().stickers().featuredEmojiSetsOrder() + : session().data().stickers().featuredSetsOrder(); } return result; } else if (_section == Section::Masks) { @@ -2252,9 +2328,15 @@ void StickersBox::Inner::rebuild(bool masks) { const auto &sets = session().data().stickers().sets(); if (_megagroupSet) { - auto usingFeatured = session().data().stickers().setsOrder().empty(); + auto usingFeatured = _megagroupSetEmoji + ? session().data().stickers().emojiSetsOrder().empty() + : session().data().stickers().setsOrder().empty(); _megagroupSubTitle->setText(usingFeatured - ? tr::lng_stickers_group_from_featured(tr::now) + ? (_megagroupSetEmoji + ? tr::lng_stickers_group_from_featured(tr::now) + : tr::lng_emoji_group_from_featured(tr::now)) + : _megagroupSetEmoji + ? tr::lng_emoji_group_from_your(tr::now) : tr::lng_stickers_group_from_your(tr::now)); updateControlsGeometry(); } else if (_isInstalledTab) { diff --git a/Telegram/SourceFiles/boxes/stickers_box.h b/Telegram/SourceFiles/boxes/stickers_box.h index 255ed3936..89b31ccf5 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.h +++ b/Telegram/SourceFiles/boxes/stickers_box.h @@ -66,7 +66,8 @@ public: StickersBox( QWidget*, std::shared_ptr show, - not_null megagroup); + not_null megagroup, + bool isEmoji); StickersBox( QWidget*, std::shared_ptr show, diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 3675c0483..543ce497d 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -1767,7 +1767,8 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) { removeSet(sets[button->section].id); } } else if (std::get_if(&pressed)) { - _show->showBox(Box(_show, _megagroupSet)); + const auto isEmoji = false; + _show->showBox(Box(_show, _megagroupSet, isEmoji)); } } } @@ -2617,7 +2618,8 @@ void StickersListWidget::setupSearch() { void StickersListWidget::displaySet(uint64 setId) { if (setId == Data::Stickers::MegagroupSetId) { if (_megagroupSet->canEditStickers()) { - checkHideWithBox(Box(_show, _megagroupSet)); + const auto isEmoji = false; + checkHideWithBox(Box(_show, _megagroupSet, isEmoji)); return; } else if (_megagroupSet->mgInfo->stickerSet.id) { setId = _megagroupSet->mgInfo->stickerSet.id; diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index 9de11be5b..2c29ef8fc 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -101,13 +101,14 @@ struct PeerUpdate { // For channels ChannelAmIn = (1ULL << 36), StickersSet = (1ULL << 37), - ChannelLinkedChat = (1ULL << 38), - ChannelLocation = (1ULL << 39), - Slowmode = (1ULL << 40), - GroupCall = (1ULL << 41), + EmojiSet = (1ULL << 38), + ChannelLinkedChat = (1ULL << 39), + ChannelLocation = (1ULL << 40), + Slowmode = (1ULL << 41), + GroupCall = (1ULL << 42), // For iteration - LastUsedBit = (1ULL << 41), + LastUsedBit = (1ULL << 42), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index cf9048a5b..831709a18 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -1163,20 +1163,34 @@ void ApplyChannelUpdate( channel->owner().botCommandsChanged(channel); } const auto stickerSet = update.vstickerset(); - const auto set = stickerSet ? &stickerSet->c_stickerSet() : nullptr; - const auto newSetId = (set ? set->vid().v : 0); - const auto oldSetId = channel->mgInfo->stickerSet.id; + const auto sset = stickerSet ? &stickerSet->c_stickerSet() : nullptr; + const auto newStickerSetId = (sset ? sset->vid().v : 0); + const auto oldStickerSetId = channel->mgInfo->stickerSet.id; const auto stickersChanged = (canEditStickers != channel->canEditStickers()) - || (oldSetId != newSetId); - if (oldSetId != newSetId) { + || (oldStickerSetId != newStickerSetId); + if (oldStickerSetId != newStickerSetId) { channel->mgInfo->stickerSet = StickerSetIdentifier{ - .id = set ? set->vid().v : 0, - .accessHash = set ? set->vaccess_hash().v : 0, + .id = sset ? sset->vid().v : 0, + .accessHash = sset ? sset->vaccess_hash().v : 0, }; } if (stickersChanged) { session->changes().peerUpdated(channel, UpdateFlag::StickersSet); } + const auto emojiSet = update.vemojiset(); + const auto eset = emojiSet ? &emojiSet->c_stickerSet() : nullptr; + const auto newEmojiSetId = (eset ? eset->vid().v : 0); + const auto oldEmojiSetId = channel->mgInfo->emojiSet.id; + const auto emojiChanged = (oldEmojiSetId != newEmojiSetId); + if (oldEmojiSetId != newEmojiSetId) { + channel->mgInfo->emojiSet = StickerSetIdentifier{ + .id = eset ? eset->vid().v : 0, + .accessHash = eset ? eset->vaccess_hash().v : 0, + }; + } + if (emojiChanged) { + session->changes().peerUpdated(channel, UpdateFlag::EmojiSet); + } channel->setBoostsUnrestrict( update.vboosts_applied().value_or_empty(), update.vboosts_unrestrict().value_or_empty()); diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index b822edda5..9cdebf5c4 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -125,6 +125,7 @@ public: bool joinedMessageFound = false; bool adminsLoaded = false; StickerSetIdentifier stickerSet; + StickerSetIdentifier emojiSet; enum LastParticipantsStatus { LastParticipantsUpToDate = 0x00, diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.cpp b/Telegram/SourceFiles/ui/boxes/boost_box.cpp index fc374dc30..25cfd75e5 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/boost_box.cpp @@ -684,6 +684,8 @@ void AskBoostBox( return tr::lng_boost_channel_title_wallpaper(); }, [&](AskBoostEmojiStatus data) { return tr::lng_boost_channel_title_status(); + }, [&](AskBoostEmojiPack data) { + return tr::lng_boost_group_title_emoji(); }, [&](AskBoostCustomReactions data) { return tr::lng_boost_channel_title_reactions(); }); @@ -694,12 +696,21 @@ void AskBoostBox( rpl::single(float64(data.requiredLevel)), Ui::Text::RichLangValue); }, [&](AskBoostWallpaper data) { - return tr::lng_boost_channel_needs_level_wallpaper( - lt_count, - rpl::single(float64(data.requiredLevel)), - Ui::Text::RichLangValue); + return (data.group + ? tr::lng_boost_group_needs_level_wallpaper + : tr::lng_boost_channel_needs_level_wallpaper)( + lt_count, + rpl::single(float64(data.requiredLevel)), + Ui::Text::RichLangValue); }, [&](AskBoostEmojiStatus data) { - return tr::lng_boost_channel_needs_level_status( + return (data.group + ? tr::lng_boost_group_needs_level_status + : tr::lng_boost_channel_needs_level_status)( + lt_count, + rpl::single(float64(data.requiredLevel)), + Ui::Text::RichLangValue); + }, [&](AskBoostEmojiPack data) { + return tr::lng_boost_group_needs_level_emoji( lt_count, rpl::single(float64(data.requiredLevel)), Ui::Text::RichLangValue); diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.h b/Telegram/SourceFiles/ui/boxes/boost_box.h index 064e697ef..78e046cba 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.h +++ b/Telegram/SourceFiles/ui/boxes/boost_box.h @@ -73,10 +73,16 @@ struct AskBoostChannelColor { struct AskBoostWallpaper { int requiredLevel = 0; + bool group = false; }; struct AskBoostEmojiStatus { int requiredLevel = 0; + bool group = false; +}; + +struct AskBoostEmojiPack { + int requiredLevel = 0; }; struct AskBoostCustomReactions { @@ -88,6 +94,7 @@ struct AskBoostReason { AskBoostChannelColor, AskBoostWallpaper, AskBoostEmojiStatus, + AskBoostEmojiPack, AskBoostCustomReactions> data; }; From 571f1a51794beab9e2c18c421885ff7aef63c94b Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 9 Feb 2024 14:32:29 +0400 Subject: [PATCH 29/73] Implement free megagroup emoji set. --- .../SourceFiles/boxes/edit_caption_box.cpp | 4 +- .../boxes/peers/edit_peer_color_box.cpp | 1 + Telegram/SourceFiles/boxes/send_files_box.cpp | 23 ++- Telegram/SourceFiles/boxes/send_files_box.h | 6 +- .../chat_helpers/emoji_list_widget.cpp | 166 ++++++++++++++++-- .../chat_helpers/emoji_list_widget.h | 12 ++ .../chat_helpers/message_field.cpp | 10 +- .../chat_helpers/tabbed_selector.cpp | 3 + Telegram/SourceFiles/data/data_channel.cpp | 4 + Telegram/SourceFiles/data/data_channel.h | 1 + .../data/stickers/data_custom_emoji.cpp | 18 +- .../data/stickers/data_custom_emoji.h | 4 +- Telegram/SourceFiles/history/history_item.cpp | 7 +- .../history/history_item_helpers.cpp | 42 ++++- .../history/history_item_helpers.h | 4 +- .../SourceFiles/history/history_widget.cpp | 10 +- .../history_view_compose_controls.cpp | 12 +- .../controls/history_view_forward_panel.cpp | 8 +- .../view/history_view_replies_section.cpp | 3 +- .../view/history_view_scheduled_section.cpp | 3 +- .../info/boosts/info_boosts_inner_widget.cpp | 4 + .../main/main_session_settings.cpp | 27 ++- .../SourceFiles/main/main_session_settings.h | 11 ++ .../media/stories/media_stories_reply.cpp | 1 + Telegram/SourceFiles/ui/boxes/boost_box.cpp | 2 - 25 files changed, 317 insertions(+), 69 deletions(-) diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index defc4bd64..8e94beeb9 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -470,8 +470,8 @@ void EditCaptionBox::rebuildPreview() { void EditCaptionBox::setupField() { const auto peer = _historyItem->history()->peer; - const auto allow = [=](const auto&) { - return Data::AllowEmojiWithoutPremium(peer); + const auto allow = [=](not_null emoji) { + return Data::AllowEmojiWithoutPremium(peer, emoji); }; InitMessageFieldHandlers( _controller, diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index 0ab69bf3b..15700114b 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -479,6 +479,7 @@ void Set( MTP_flags(Flag::f_color | Flag::f_background_emoji_id), MTP_int(values.colorIndex), MTP_long(values.backgroundEmojiId))); + } else if (peer->isMegagroup()) { } else if (const auto channel = peer->asChannel()) { using Flag = MTPchannels_UpdateColor::Flag; send(MTPchannels_UpdateColor( diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 7cd37dbe8..8b871cb63 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -335,16 +335,16 @@ SendFilesBox::SendFilesBox( not_null controller, Ui::PreparedList &&list, const TextWithTags &caption, - SendFilesLimits limits, - SendFilesCheck check, + not_null toPeer, Api::SendType sendType, SendMenu::Type sendMenuType) : SendFilesBox(nullptr, { .show = controller->uiShow(), .list = std::move(list), .caption = caption, - .limits = limits, - .check = check, + .captionToPeer = toPeer, + .limits = DefaultLimitsForPeer(toPeer), + .check = DefaultCheckForPeer(controller, toPeer), .sendType = sendType, .sendMenuType = sendMenuType, }) { @@ -360,6 +360,7 @@ SendFilesBox::SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor) , _list(std::move(descriptor.list)) , _limits(descriptor.limits) , _sendMenuType(descriptor.sendMenuType) +, _captionToPeer(descriptor.captionToPeer) , _check(std::move(descriptor.check)) , _confirmedCallback(std::move(descriptor.confirmed)) , _cancelledCallback(std::move(descriptor.cancelled)) @@ -1000,8 +1001,10 @@ void SendFilesBox::updateSendWayControls() { } void SendFilesBox::setupCaption() { - const auto allow = [=](const auto &) { - return (_limits & SendFilesAllow::EmojiWithoutPremium); + const auto allow = [=](not_null emoji) { + return _captionToPeer + ? Data::AllowEmojiWithoutPremium(_captionToPeer, emoji) + : (_limits & SendFilesAllow::EmojiWithoutPremium); }; const auto show = _show; InitMessageFieldHandlers( @@ -1113,7 +1116,6 @@ void SendFilesBox::setupEmojiPanel() { .level = Window::GifPauseReason::Layer, .mode = ChatHelpers::TabbedSelector::Mode::EmojiOnly, .features = { - .megagroupSet = false, .stickersSettings = false, .openStickerSets = false, }, @@ -1124,6 +1126,7 @@ void SendFilesBox::setupEmojiPanel() { st::emojiPanMinHeight / 2, st::emojiPanMinHeight); _emojiPanel->hide(); + _emojiPanel->selector()->setCurrentPeer(_captionToPeer); _emojiPanel->selector()->setAllowEmojiWithoutPremium( _limits & SendFilesAllow::EmojiWithoutPremium); _emojiPanel->selector()->emojiChosen( @@ -1136,7 +1139,11 @@ void SendFilesBox::setupEmojiPanel() { if (info && info->setType == Data::StickersType::Emoji && !_show->session().premium() - && !(_limits & SendFilesAllow::EmojiWithoutPremium)) { + && !(_captionToPeer + ? Data::AllowEmojiWithoutPremium( + _captionToPeer, + data.document) + : (_limits & SendFilesAllow::EmojiWithoutPremium))) { ShowPremiumPreviewBox(_show, PremiumPreview::AnimatedEmoji); } else { Data::InsertCustomEmoji(_caption.data(), data.document); diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index 0acbb8f49..af712eda1 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -92,6 +92,7 @@ struct SendFilesBoxDescriptor { std::shared_ptr show; Ui::PreparedList list; TextWithTags caption; + PeerData *captionToPeer = nullptr; SendFilesLimits limits = {}; SendFilesCheck check; Api::SendType sendType = {}; @@ -112,8 +113,7 @@ public: not_null controller, Ui::PreparedList &&list, const TextWithTags &caption, - SendFilesLimits limits, - SendFilesCheck check, + not_null toPeer, Api::SendType sendType, SendMenu::Type sendMenuType); SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor); @@ -239,7 +239,7 @@ private: SendFilesLimits _limits = {}; SendMenu::Type _sendMenuType = {}; - + PeerData *_captionToPeer = nullptr; SendFilesCheck _check; SendFilesConfirmed _confirmedCallback; Fn _cancelledCallback; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 2d27f1db4..224dafa08 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -31,6 +31,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "layout/layout_position.h" #include "data/data_session.h" +#include "data/data_changes.h" +#include "data/data_channel.h" #include "data/data_document.h" #include "data/data_peer_values.h" #include "data/stickers/data_stickers.h" @@ -41,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "emoji_suggestions_data.h" #include "emoji_suggestions_helper.h" #include "main/main_session.h" +#include "main/main_session_settings.h" #include "core/core_settings.h" #include "core/application.h" #include "settings/settings_premium.h" @@ -462,6 +465,7 @@ EmojiListWidget::EmojiListWidget( , _show(std::move(descriptor.show)) , _features(descriptor.features) , _mode(descriptor.mode) +, _api(&session().mtp()) , _staticCount(_mode == Mode::Full ? kEmojiSectionCount : 1) , _premiumIcon(_mode == Mode::EmojiStatus ? std::make_unique() @@ -517,6 +521,15 @@ EmojiListWidget::EmojiListWidget( pickerHidden(); }, lifetime()); + session().changes().peerUpdates( + Data::PeerUpdate::Flag::EmojiSet + ) | rpl::filter([=](const Data::PeerUpdate &update) { + return (update.peer.get() == _megagroupSet); + }) | rpl::start_with_next([=] { + refreshCustom(); + resizeToWidth(width()); + }, lifetime()); + session().data().stickers().updated( Data::StickersType::Emoji ) | rpl::start_with_next([=] { @@ -1678,6 +1691,13 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { } void EmojiListWidget::displaySet(uint64 setId) { + if (setId == Data::Stickers::MegagroupSetId) { + if (_megagroupSet->mgInfo->emojiSet.id) { + setId = _megagroupSet->mgInfo->emojiSet.id; + } else { + return; + } + } const auto &sets = session().data().stickers().sets(); auto it = sets.find(setId); if (it != sets.cend()) { @@ -1685,9 +1705,37 @@ void EmojiListWidget::displaySet(uint64 setId) { } } +void EmojiListWidget::removeMegagroupSet(bool locally) { + if (locally) { + session().settings().setGroupEmojiSectionHidden(_megagroupSet->id); + session().saveSettings(); + refreshCustom(); + return; + } + checkHideWithBox(Ui::MakeConfirmBox({ + .text = tr::lng_emoji_remove_group_set(), + .confirmed = crl::guard(this, [this, group = _megagroupSet]( + Fn &&close) { + Expects(group->mgInfo != nullptr); + + if (group->mgInfo->emojiSet) { + session().api().setGroupEmojiSet(group, {}); + } + close(); + }), + .cancelled = [](Fn &&close) { close(); }, + .labelStyle = &st().boxLabel, + })); +} + void EmojiListWidget::removeSet(uint64 setId) { const auto &labelSt = st().boxLabel; - if (auto box = MakeConfirmRemoveSetBox(&session(), labelSt, setId)) { + if (setId == Data::Stickers::MegagroupSetId) { + const auto i = ranges::find(_custom, setId, &CustomSet::id); + Assert(i != end(_custom)); + const auto removeLocally = !_megagroupSet->canEditEmoji(); + removeMegagroupSet(removeLocally); + } else if (auto box = MakeConfirmRemoveSetBox(&session(), labelSt, setId)) { checkHideWithBox(std::move(box)); } } @@ -1784,6 +1832,13 @@ bool EmojiListWidget::hasRemoveButton(int index) const { return false; } const auto &set = _custom[index - _staticCount]; + if (set.id == Data::Stickers::MegagroupSetId) { + Assert(_megagroupSet != nullptr); + if (index + 1 != _staticCount + _custom.size()) { + return true; + } + return !set.list.empty() && _megagroupSet->canEditEmoji(); + } return set.canRemove && !set.premiumRequired; } @@ -1810,7 +1865,9 @@ bool EmojiListWidget::hasAddButton(int index) const { return false; } const auto &set = _custom[index - _staticCount]; - return !set.canRemove && !set.premiumRequired; + return !set.canRemove + && !set.premiumRequired + && set.id != Data::Stickers::MegagroupSetId; } QRect EmojiListWidget::addButtonRect(int index) const { @@ -1834,10 +1891,13 @@ QRect EmojiListWidget::unlockButtonRect(int index) const { } bool EmojiListWidget::hasButton(int index) const { - if (hasColorButton(index) - || (index >= _staticCount - && index < _staticCount + _custom.size())) { + if (hasColorButton(index)) { return true; + } else if (index >= _staticCount + && index < _staticCount + _custom.size()) { + const auto &custom = _custom[index - _staticCount]; + return (custom.id != Data::Stickers::MegagroupSetId) + || custom.canRemove; } return false; } @@ -1967,6 +2027,16 @@ void EmojiListWidget::setAllowWithoutPremium(bool allow) { resizeToWidth(width()); } +void EmojiListWidget::showMegagroupSet(ChannelData *megagroup) { + Expects(!megagroup || megagroup->isMegagroup()); + + if (_megagroupSet != megagroup) { + _megagroupSet = megagroup; + refreshCustom(); + resizeToWidth(width()); + } +} + QString EmojiListWidget::tooltipText() const { if (_mode != Mode::Full) { return {}; @@ -2037,7 +2107,15 @@ void EmojiListWidget::refreshCustom() { const auto owner = &session->data(); const auto &sets = owner->stickers().sets(); const auto push = [&](uint64 setId, bool installed) { - auto it = sets.find(setId); + const auto megagroup = _megagroupSet + && (setId == Data::Stickers::MegagroupSetId); + const auto lookupId = megagroup + ? _megagroupSet->mgInfo->emojiSet.id + : setId; + if (!lookupId) { + return; + } + auto it = sets.find(lookupId); if (it == sets.cend() || it->second->stickers.isEmpty() || (_mode == Mode::BackgroundEmoji && !it->second->textColor()) @@ -2045,12 +2123,13 @@ void EmojiListWidget::refreshCustom() { && !it->second->channelStatus())) { return; } - const auto canRemove = !!(it->second->flags - & Data::StickersSetFlag::Installed); + const auto canRemove = megagroup + ? (_megagroupSet->canEditEmoji() || installed) + : !!(it->second->flags & Data::StickersSetFlag::Installed); const auto sortAsInstalled = canRemove && (!(it->second->flags & Data::StickersSetFlag::Featured) - || !_localSetsManager->isInstalledLocally(setId)); - if (sortAsInstalled != installed) { + || !_localSetsManager->isInstalledLocally(lookupId)); + if (!megagroup && sortAsInstalled != installed) { return; } auto premium = false; @@ -2063,7 +2142,7 @@ void EmojiListWidget::refreshCustom() { return false; } for (auto k = 0; k != count; ++k) { - if (!premium && list[k]->isPremiumEmoji()) { + if (!premium && !megagroup && list[k]->isPremiumEmoji()) { premium = true; } if (i->list[k].document != list[k]) { @@ -2097,11 +2176,11 @@ void EmojiListWidget::refreshCustom() { continue; } else if (const auto sticker = document->sticker()) { set.push_back({ - .custom = resolveCustomEmoji(document, setId), + .custom = resolveCustomEmoji(document, lookupId), .document = document, .emoji = Ui::Emoji::Find(sticker->alt), }); - if (!premium && document->isPremiumEmoji()) { + if (!premium && !megagroup && document->isPremiumEmoji()) { premium = true; } } @@ -2119,12 +2198,14 @@ void EmojiListWidget::refreshCustom() { .premiumRequired = premium && premiumMayBeBought, }); }; + refreshMegagroupStickers(push, GroupStickersPlace::Visible); for (const auto setId : owner->stickers().emojiSetsOrder()) { push(setId, true); } for (const auto setId : owner->stickers().featuredEmojiSetsOrder()) { push(setId, false); } + refreshMegagroupStickers(push, GroupStickersPlace::Hidden); _footer->refreshIcons( fillIcons(), @@ -2217,6 +2298,60 @@ not_null EmojiListWidget::resolveCustomRecent( ).first->second.emoji.get(); } +void EmojiListWidget::refreshMegagroupStickers( + Fn push, + GroupStickersPlace place) { + if (!_features.megagroupSet + || !_megagroupSet + || !_megagroupSet->mgInfo->emojiSet) { + return; + } + auto canEdit = _megagroupSet->canEditEmoji(); + auto isShownHere = [place](bool hidden) { + return (hidden == (place == GroupStickersPlace::Hidden)); + }; + auto hidden = session().settings().isGroupEmojiSectionHidden(_megagroupSet->id); + auto removeHiddenForGroup = [this, &hidden] { + if (hidden) { + session().settings().removeGroupEmojiSectionHidden(_megagroupSet->id); + session().saveSettings(); + hidden = false; + } + }; + if (canEdit && hidden) { + removeHiddenForGroup(); + } + const auto &set = _megagroupSet->mgInfo->emojiSet; + if (!set.id || !isShownHere(hidden)) { + return; + } + push(Data::Stickers::MegagroupSetId, !hidden); + if (!_custom.empty() + && _custom.back().id == Data::Stickers::MegagroupSetId) { + return; + } else if (_megagroupSetIdRequested == set.id) { + return; + } + _megagroupSetIdRequested = set.id; + _api.request(MTPmessages_GetStickerSet( + Data::InputStickerSet(set), + MTP_int(0) // hash + )).done([=](const MTPmessages_StickerSet &result) { + result.match([&](const MTPDmessages_stickerSet &data) { + if (const auto set = session().data().stickers().feedSetFull(data)) { + refreshCustom(); + if (set->id == _megagroupSetIdRequested) { + _megagroupSetIdRequested = 0; + } else { + LOG(("API Error: Got different set.")); + } + } + }, [](const MTPDmessages_stickerSetNotModified &) { + LOG(("API Error: Unexpected messages.stickerSetNotModified.")); + }); + }).send(); +} + std::vector EmojiListWidget::fillIcons() { auto result = std::vector(); result.reserve(2 + _custom.size()); @@ -2233,6 +2368,11 @@ std::vector EmojiListWidget::fillIcons() { } const auto esize = StickersListFooter::IconFrameSize(); for (const auto &custom : _custom) { + if (custom.id == Data::Stickers::MegagroupSetId) { + result.emplace_back(Data::Stickers::MegagroupSetId); + result.back().megagroup = _megagroupSet; + continue; + } const auto set = custom.set; result.emplace_back(set, custom.thumbnailDocument, esize, esize); } diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index c2a8ef3ed..0f1c897a6 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -117,6 +117,7 @@ public: void showSet(uint64 setId); [[nodiscard]] uint64 currentSet(int yOffset) const; void setAllowWithoutPremium(bool allow); + void showMegagroupSet(ChannelData *megagroup); // Ui::AbstractTooltipShower interface. QString tooltipText() const override; @@ -257,6 +258,13 @@ private: void colorChosen(EmojiChosen data); bool checkPickerHide(); void refreshCustom(); + enum class GroupStickersPlace { + Visible, + Hidden, + }; + void refreshMegagroupStickers( + Fn push, + GroupStickersPlace place); void unloadNotSeenCustom(int visibleTop, int visibleBottom); void unloadAllCustom(); void unloadCustomIn(const SectionInfo &info); @@ -340,6 +348,7 @@ private: void displaySet(uint64 setId); void removeSet(uint64 setId); + void removeMegagroupSet(bool locally); void initButton(RightButton &button, const QString &text, bool gradient); [[nodiscard]] std::unique_ptr createButtonRipple( @@ -368,10 +377,13 @@ private: const ComposeFeatures _features; Mode _mode = Mode::Full; std::unique_ptr _search; + MTP::Sender _api; const int _staticCount = 0; StickersListFooter *_footer = nullptr; std::unique_ptr _premiumIcon; std::unique_ptr _localSetsManager; + ChannelData *_megagroupSet = nullptr; + uint64 _megagroupSetIdRequested = 0; Fn( DocumentId, Fn)> _customRecentFactory; diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 77ca3bdd3..85c8f9dda 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_widget.h" #include "history/history.h" // History::session #include "history/history_item.h" // HistoryItem::originalText -#include "history/history_item_helpers.h" // DropCustomEmoji +#include "history/history_item_helpers.h" // DropDisallowedCustomEmoji #include "base/qthelp_regex.h" #include "base/qthelp_url.h" #include "base/event_filter.h" @@ -278,11 +278,9 @@ TextWithTags PrepareEditText(not_null item) { auto original = item->history()->session().supportMode() ? StripSupportHashtag(item->originalText()) : item->originalText(); - const auto dropCustomEmoji = !item->history()->session().premium() - && !item->history()->peer->isSelf(); - if (dropCustomEmoji) { - original = DropCustomEmoji(std::move(original)); - } + original = DropDisallowedCustomEmoji( + item->history()->peer, + std::move(original)); return TextWithTags{ original.text, TextUtilities::ConvertEntitiesToTextTags(original.entities) diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index 6fc2c555a..5841f2c9e 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -966,6 +966,9 @@ void TabbedSelector::setCurrentPeer(PeerData *peer) { } _currentPeer = peer; checkRestrictedPeer(); + if (hasEmojiTab()) { + emoji()->showMegagroupSet(peer ? peer->asMegagroup() : nullptr); + } if (hasStickersTab()) { stickers()->showMegagroupSet(peer ? peer->asMegagroup() : nullptr); } diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 831709a18..0c73b00b6 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -648,6 +648,10 @@ bool ChannelData::canEditStickers() const { return (flags() & Flag::CanSetStickers); } +bool ChannelData::canEditEmoji() const { + return amCreator(); AssertIsDebug(); +} + bool ChannelData::canDelete() const { constexpr auto kDeleteChannelMembersLimit = 1000; return amCreator() diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 9cdebf5c4..afeb20cbd 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -361,6 +361,7 @@ public: [[nodiscard]] bool canViewBanned() const; [[nodiscard]] bool canEditSignatures() const; [[nodiscard]] bool canEditStickers() const; + [[nodiscard]] bool canEditEmoji() const; [[nodiscard]] bool canDelete() const; [[nodiscard]] bool canEditAdmin(not_null user) const; [[nodiscard]] bool canRestrictParticipant( diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 5b271b5b6..0e40b7792 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_account.h" #include "main/main_app_config.h" #include "main/main_session.h" +#include "data/data_channel.h" #include "data/data_session.h" #include "data/data_document.h" #include "data/data_document_media.h" @@ -980,8 +981,21 @@ TextWithEntities SingleCustomEmoji(not_null document) { return SingleCustomEmoji(document->id); } -bool AllowEmojiWithoutPremium(not_null peer) { - return peer->isSelf(); +bool AllowEmojiWithoutPremium( + not_null peer, + DocumentData *exactEmoji) { + if (peer->isSelf()) { + return true; + } else if (!exactEmoji) { + return false; + } else if (const auto sticker = exactEmoji->sticker()) { + if (const auto channel = peer->asMegagroup()) { + if (channel->mgInfo->emojiSet.id == sticker->set.id) { + return (sticker->set.id != 0); + } + } + } + return false; } void InsertCustomEmoji( diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h index 947f8bfda..49799e639 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h @@ -201,7 +201,9 @@ private: [[nodiscard]] TextWithEntities SingleCustomEmoji( not_null document); -[[nodiscard]] bool AllowEmojiWithoutPremium(not_null peer); +[[nodiscard]] bool AllowEmojiWithoutPremium( + not_null peer, + DocumentData *exactEmoji = nullptr); void InsertCustomEmoji( not_null field, diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 79fef7373..aa815ccc9 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -564,11 +564,8 @@ HistoryItem::HistoryItem( } } - const auto dropCustomEmoji = dropForwardInfo - && !history->session().premium() - && !history->peer->isSelf(); - setText(dropCustomEmoji - ? DropCustomEmoji(original->originalText()) + setText(dropForwardInfo + ? DropDisallowedCustomEmoji(history->peer, original->originalText()) : original->originalText()); } diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index f3aba166e..911ec9068 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_text_entities.h" #include "boxes/premium_preview_box.h" #include "calls/calls_instance.h" +#include "data/stickers/data_custom_emoji.h" #include "data/notify/data_notify_settings.h" #include "data/data_channel.h" #include "data/data_chat.h" @@ -217,13 +218,40 @@ bool LookupReplyIsTopicPost(HistoryItem *replyTo) { && (replyTo->topicRootId() != Data::ForumTopic::kGeneralId); } -TextWithEntities DropCustomEmoji(TextWithEntities text) { - text.entities.erase( - ranges::remove( - text.entities, - EntityType::CustomEmoji, - &EntityInText::type), - text.entities.end()); +TextWithEntities DropDisallowedCustomEmoji( + not_null to, + TextWithEntities text) { + if (to->session().premium() || to->isSelf()) { + return text; + } + const auto channel = to->asMegagroup(); + const auto allowSetId = channel ? channel->mgInfo->emojiSet.id : 0; + if (!allowSetId) { + text.entities.erase( + ranges::remove( + text.entities, + EntityType::CustomEmoji, + &EntityInText::type), + text.entities.end()); + } else { + const auto predicate = [&](const EntityInText &entity) { + if (entity.type() != EntityType::CustomEmoji) { + return false; + } + if (const auto id = Data::ParseCustomEmojiData(entity.data())) { + const auto document = to->owner().document(id); + if (const auto sticker = document->sticker()) { + if (sticker->set.id == allowSetId) { + return false; + } + } + } + return true; + }; + text.entities.erase( + ranges::remove_if(text.entities, predicate), + text.entities.end()); + } return text; } diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index 5ebb3f0de..e468f1497 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -111,7 +111,9 @@ struct SendingErrorRequest { not_null thread, SendingErrorRequest request); -[[nodiscard]] TextWithEntities DropCustomEmoji(TextWithEntities text); +[[nodiscard]] TextWithEntities DropDisallowedCustomEmoji( + not_null to, + TextWithEntities text); [[nodiscard]] Main::Session *SessionByUniqueId(uint64 sessionUniqueId); [[nodiscard]] HistoryItem *MessageByGlobalId(GlobalMsgId globalId); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index e003b6020..ba20e32ca 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -395,7 +395,7 @@ HistoryWidget::HistoryWidget( InitMessageField(controller, _field, [=]( not_null document) { - if (_peer && Data::AllowEmojiWithoutPremium(_peer)) { + if (_peer && Data::AllowEmojiWithoutPremium(_peer, document)) { return true; } showPremiumToast(document); @@ -1091,7 +1091,10 @@ void HistoryWidget::initTabbedSelector() { ; info && info->setType == Data::StickersType::Emoji) { if (data.document->isPremiumEmoji() && !session().premium() - && (!_peer || !Data::AllowEmojiWithoutPremium(_peer))) { + && (!_peer + || !Data::AllowEmojiWithoutPremium( + _peer, + data.document))) { showPremiumToast(data.document); } else if (!_field->isHidden()) { Data::InsertCustomEmoji(_field.data(), data.document); @@ -5474,8 +5477,7 @@ bool HistoryWidget::confirmSendingFiles( controller(), std::move(list), text, - DefaultLimitsForPeer(_peer), - DefaultCheckForPeer(controller(), _peer), + _peer, Api::SendType::Normal, sendMenuType()); _field->setTextWithTags({}); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 19ec21b33..cc960fe9b 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1572,7 +1572,8 @@ void ComposeControls::initField() { }, _field->lifetime()); #endif // Q_OS_MAC InitMessageField(_show, _field, [=](not_null emoji) { - if (_history && Data::AllowEmojiWithoutPremium(_history->peer)) { + if (_history + && Data::AllowEmojiWithoutPremium(_history->peer, emoji)) { return true; } if (_unavailableEmojiPasted) { @@ -1584,8 +1585,9 @@ void ComposeControls::initField() { _field->setEditLinkCallback( DefaultEditLinkCallback(_show, _field, &_st.boxField)); initAutocomplete(); - const auto allow = [=](const auto &) { - return _history && Data::AllowEmojiWithoutPremium(_history->peer); + const auto allow = [=](not_null emoji) { + return _history + && Data::AllowEmojiWithoutPremium(_history->peer, emoji); }; const auto suggestions = Ui::Emoji::SuggestionsController::Init( _parent, @@ -2087,7 +2089,9 @@ void ComposeControls::initTabbedSelector() { if (data.document->isPremiumEmoji() && !session().premium() && (!_history - || !Data::AllowEmojiWithoutPremium(_history->peer))) { + || !Data::AllowEmojiWithoutPremium( + _history->peer, + data.document))) { if (_unavailableEmojiPasted) { _unavailableEmojiPasted(data.document); } 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 13c6dace6..ca783f07a 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -194,12 +194,8 @@ void ForwardPanel::updateTexts() { .generateImages = false, .ignoreGroup = true, }).text; - const auto history = item->history(); - const auto dropCustomEmoji = !history->session().premium() - && !_to->peer()->isSelf() - && (item->computeDropForwardedInfo() || !keepNames); - if (dropCustomEmoji) { - text = DropCustomEmoji(std::move(text)); + if (item->computeDropForwardedInfo() || !keepNames) { + text = DropDisallowedCustomEmoji(_to->peer(), std::move(text)); } } else { text = Ui::Text::Colorized( diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index ef330f374..1043cd596 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -964,8 +964,7 @@ bool RepliesWidget::confirmSendingFiles( controller(), std::move(list), _composeControls->getTextWithAppliedMarkdown(), - DefaultLimitsForPeer(_history->peer), - DefaultCheckForPeer(controller(), _history->peer), + _history->peer, Api::SendType::Normal, SendMenu::Type::SilentOnly); // #TODO replies schedule diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 581de48a5..dae80a18d 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -416,8 +416,7 @@ bool ScheduledWidget::confirmSendingFiles( controller(), std::move(list), _composeControls->getTextWithAppliedMarkdown(), - DefaultLimitsForPeer(_history->peer), - DefaultCheckForPeer(controller(), _history->peer), + _history->peer, (CanScheduleUntilOnline(_history->peer) ? Api::SendType::ScheduledToUser : Api::SendType::Scheduled), diff --git a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp index 076b34532..eb0805c49 100644 --- a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp +++ b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp @@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/slide_wrap.h" #include "styles/style_giveaway.h" #include "styles/style_info.h" +#include "styles/style_premium.h" #include "styles/style_statistics.h" #include @@ -301,6 +302,9 @@ void InnerWidget::fill() { { auto dividerContent = object_ptr(inner); + dividerContent->add(object_ptr( + dividerContent, + st::boostSkipTop)); Ui::FillBoostLimit( fakeShowed->events(), dividerContent.data(), diff --git a/Telegram/SourceFiles/main/main_session_settings.cpp b/Telegram/SourceFiles/main/main_session_settings.cpp index 769cd6998..cf451a20b 100644 --- a/Telegram/SourceFiles/main/main_session_settings.cpp +++ b/Telegram/SourceFiles/main/main_session_settings.cpp @@ -44,7 +44,9 @@ QByteArray SessionSettings::serialize() const { + sizeof(qint32) + (_mutePeriods.size() * sizeof(quint64)) + sizeof(qint32) * 2 - + _hiddenPinnedMessages.size() * (sizeof(quint64) * 3); + + _hiddenPinnedMessages.size() * (sizeof(quint64) * 3) + + sizeof(qint32) + + _groupEmojiSectionHidden.size() * sizeof(quint64); auto result = QByteArray(); result.reserve(size); @@ -91,6 +93,11 @@ QByteArray SessionSettings::serialize() const { << qint64(key.topicRootId.bare) << qint64(value.bare); } + stream + << qint32(_groupEmojiSectionHidden.size()); + for (const auto &peerId : _groupEmojiSectionHidden) { + stream << SerializePeerId(peerId); + } } return result; } @@ -114,6 +121,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { qint32 appFloatPlayerCorner = static_cast(RectPart::TopRight); base::flat_map appSoundOverrides; base::flat_set groupStickersSectionHidden; + base::flat_set groupEmojiSectionHidden; qint32 appThirdSectionInfoEnabled = 0; qint32 legacySmallDialogsList = 0; float64 appDialogsWidthRatio = app.dialogsWidthRatio(); @@ -410,6 +418,22 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { } } } + if (!stream.atEnd()) { + auto count = qint32(0); + stream >> count; + if (stream.status() == QDataStream::Ok) { + for (auto i = 0; i != count; ++i) { + quint64 peerId; + stream >> peerId; + if (stream.status() != QDataStream::Ok) { + LOG(("App Error: " + "Bad data for SessionSettings::addFromSerialized()")); + return; + } + groupEmojiSectionHidden.emplace(DeserializePeerId(peerId)); + } + } + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for SessionSettings::addFromSerialized()")); @@ -433,6 +457,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { case ChatHelpers::SelectorTab::Gifs: _selectorTab = uncheckedTab; break; } _groupStickersSectionHidden = std::move(groupStickersSectionHidden); + _groupEmojiSectionHidden = std::move(groupEmojiSectionHidden); auto uncheckedSupportSwitch = static_cast( supportSwitch); switch (uncheckedSupportSwitch) { diff --git a/Telegram/SourceFiles/main/main_session_settings.h b/Telegram/SourceFiles/main/main_session_settings.h index 44d9e9a4a..222a94ead 100644 --- a/Telegram/SourceFiles/main/main_session_settings.h +++ b/Telegram/SourceFiles/main/main_session_settings.h @@ -75,6 +75,16 @@ public: _groupStickersSectionHidden.remove(peerId); } + void setGroupEmojiSectionHidden(PeerId peerId) { + _groupEmojiSectionHidden.insert(peerId); + } + [[nodiscard]] bool isGroupEmojiSectionHidden(PeerId peerId) const { + return _groupEmojiSectionHidden.contains(peerId); + } + void removeGroupEmojiSectionHidden(PeerId peerId) { + _groupEmojiSectionHidden.remove(peerId); + } + void setMediaLastPlaybackPosition(DocumentId id, crl::time time); [[nodiscard]] crl::time mediaLastPlaybackPosition(DocumentId id) const; @@ -137,6 +147,7 @@ private: ChatHelpers::SelectorTab _selectorTab; // per-window base::flat_set _groupStickersSectionHidden; + base::flat_set _groupEmojiSectionHidden; bool _hadLegacyCallsPeerToPeerNobody = false; Data::AutoDownload::Full _autoDownload; rpl::variable _archiveCollapsed = false; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index d06f32dbb..dba4cb02b 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -505,6 +505,7 @@ bool ReplyArea::confirmSendingFiles( .show = show, .list = std::move(list), .caption = _controls->getTextWithAppliedMarkdown(), + .captionToPeer = _data.peer, .limits = DefaultLimitsForPeer(_data.peer), .check = DefaultCheckForPeer(show, _data.peer), .sendType = Api::SendType::Normal, diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.cpp b/Telegram/SourceFiles/ui/boxes/boost_box.cpp index 25cfd75e5..f4676dcc5 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/boost_box.cpp @@ -777,8 +777,6 @@ void FillBoostLimit( container->add(object_ptr(container, skip)); }; - //addSkip(st::boostSkipTop); - const auto ratio = [=](BoostCounters counters) { const auto min = counters.thisLevelBoosts; const auto max = counters.nextLevelBoosts; From cfaef4c441442a4f7319c3eb249b7e9fc4505c20 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 9 Feb 2024 18:09:33 +0400 Subject: [PATCH 30/73] Add Boost Group in group menu. --- Telegram/SourceFiles/window/window_peer_menu.cpp | 6 ++++++ Telegram/SourceFiles/window/window_session_controller.h | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 0e3c48cdb..10999c91c 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1054,6 +1054,12 @@ void Filler::addViewStatistics() { controller->showSection(Info::Boosts::Make(peer)); } }, &st::menuIconBoosts); + } else { + _addAction(tr::lng_boost_group_button(tr::now), [=] { + if (const auto strong = weak.get()) { + controller->resolveBoostState(channel); + } + }, &st::menuIconBoosts); } } } diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 7aa32404d..57e528579 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -243,6 +243,8 @@ public: void searchInChat(Dialogs::Key inChat); void searchMessages(const QString &query, Dialogs::Key inChat); + void resolveBoostState(not_null channel); + base::weak_ptr showToast( Ui::Toast::Config &&config); base::weak_ptr showToast( @@ -279,7 +281,6 @@ private: not_null peer, const PeerByLinkInfo &info); - void resolveBoostState(not_null channel); void applyBoost( not_null channel, Fn done); From fc6f2520b7d9cdf1dd63f115fc6ba16247956289 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 9 Feb 2024 18:09:48 +0400 Subject: [PATCH 31/73] Support free transcribes in groups. --- Telegram/SourceFiles/api/api_transcribes.cpp | 9 ++++++++ Telegram/SourceFiles/api/api_transcribes.h | 2 ++ .../boxes/peers/edit_peer_info_box.cpp | 4 +--- Telegram/SourceFiles/data/data_channel.cpp | 2 +- Telegram/SourceFiles/data/data_session.cpp | 22 ++++++++++++------- Telegram/SourceFiles/data/data_session.h | 2 ++ .../view/history_view_transcribe_button.cpp | 12 +++++----- .../view/media/history_view_document.cpp | 10 +++++---- 8 files changed, 41 insertions(+), 22 deletions(-) diff --git a/Telegram/SourceFiles/api/api_transcribes.cpp b/Telegram/SourceFiles/api/api_transcribes.cpp index ac792883b..d7064abcd 100644 --- a/Telegram/SourceFiles/api/api_transcribes.cpp +++ b/Telegram/SourceFiles/api/api_transcribes.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_transcribes.h" #include "apiwrap.h" +#include "data/data_channel.h" #include "data/data_document.h" #include "data/data_peer.h" #include "data/data_session.h" @@ -25,6 +26,14 @@ Transcribes::Transcribes(not_null api) , _api(&api->instance()) { } +bool Transcribes::freeFor(not_null item) const { + if (const auto channel = item->history()->peer->asMegagroup()) { + const auto owner = &channel->owner(); + return channel->levelHint() >= owner->groupFreeTranscribeLevel(); + } + return false; +} + bool Transcribes::trialsSupport() { if (!_trialsSupport) { const auto count = _session->account().appConfig().get( diff --git a/Telegram/SourceFiles/api/api_transcribes.h b/Telegram/SourceFiles/api/api_transcribes.h index a5c5923ec..b074e42f1 100644 --- a/Telegram/SourceFiles/api/api_transcribes.h +++ b/Telegram/SourceFiles/api/api_transcribes.h @@ -36,6 +36,8 @@ public: void apply(const MTPDupdateTranscribedAudio &update); + [[nodiscard]] bool freeFor(not_null item) const; + [[nodiscard]] bool trialsSupport(); [[nodiscard]] TimeId trialsRefreshAt(); [[nodiscard]] int trialsCount(); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 7b9abb4c1..01bbb02f2 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -1094,9 +1094,7 @@ void Controller::fillManageSection() { && (channel->hasAdminRights() || channel->amCreator()); const auto canEditStickers = isChannel && channel->canEditStickers(); const auto canDeleteChannel = isChannel && channel->canDelete(); - const auto canEditColorIndex = isChannel - && (channel->amCreator() - || (channel->adminRights() & ChatAdminRight::ChangeInfo)); + const auto canEditColorIndex = isChannel && channel->canEditEmoji(); const auto canViewOrEditLinkedChat = isChannel && (channel->linkedChat() || (channel->isBroadcast() && channel->canEditInformation())); diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 0c73b00b6..87d2a5301 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -649,7 +649,7 @@ bool ChannelData::canEditStickers() const { } bool ChannelData::canEditEmoji() const { - return amCreator(); AssertIsDebug(); + return amCreator() || (adminRights() & ChatAdminRight::ChangeInfo); } bool ChannelData::canDelete() const { diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index f64717794..c8235eac4 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -238,6 +238,12 @@ Session::Session(not_null session) , _bigFileCache(Core::App().databases().get( _session->local().cacheBigFilePath(), _session->local().cacheBigFileSettings())) +, _groupFreeTranscribeLevel(session->account().appConfig().value( +) | rpl::map([=] { + return session->account().appConfig().get( + u"group_transcribe_level_min"_q, + 6); +})) , _chatsList( session, FilterId(), @@ -2178,8 +2184,7 @@ rpl::producer Session::maxPinnedChatsLimitValue( // 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() + return _session->account().appConfig().value( ) | rpl::map([=] { const auto limits = Data::PremiumLimits(_session); return folder @@ -2194,8 +2199,7 @@ rpl::producer Session::maxPinnedChatsLimitValue( // 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() + return _session->account().appConfig().value( ) | rpl::map([=] { const auto limits = Data::PremiumLimits(_session); return limits.dialogFiltersChatsPremium(); @@ -2204,8 +2208,7 @@ rpl::producer Session::maxPinnedChatsLimitValue( rpl::producer Session::maxPinnedChatsLimitValue( not_null forum) const { - return rpl::single(rpl::empty_value()) | rpl::then( - _session->account().appConfig().refreshed() + return _session->account().appConfig().value( ) | rpl::map([=] { const auto limits = Data::PremiumLimits(_session); return limits.topicsPinnedCurrent(); @@ -2218,14 +2221,17 @@ rpl::producer Session::maxPinnedChatsLimitValue( // 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() + return _session->account().appConfig().value( ) | rpl::map([=] { const auto limits = Data::PremiumLimits(_session); return limits.savedSublistsPinnedPremium(); }); } +int Session::groupFreeTranscribeLevel() const { + return _groupFreeTranscribeLevel.current(); +} + const std::vector &Session::pinnedChatsOrder( Data::Folder *folder) const { return chatsList(folder)->pinned()->order(); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 0a779a4b8..d391d1d31 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -367,6 +367,7 @@ public: not_null forum) const; [[nodiscard]] rpl::producer maxPinnedChatsLimitValue( not_null saved) const; + [[nodiscard]] int groupFreeTranscribeLevel() const; [[nodiscard]] const std::vector &pinnedChatsOrder( Folder *folder) const; [[nodiscard]] const std::vector &pinnedChatsOrder( @@ -887,6 +888,7 @@ private: QPointer _exportSuggestion; rpl::variable _contactsLoaded = false; + rpl::variable _groupFreeTranscribeLevel; rpl::event_stream _chatsListLoadedEvents; rpl::event_stream _chatsListChanged; rpl::event_stream> _userIsBotChanges; diff --git a/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp b/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp index 523cd5ce1..dccca0f3a 100644 --- a/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp @@ -221,14 +221,14 @@ void TranscribeButton::paint( } bool TranscribeButton::hasLock() const { - if (_item->history()->session().premium()) { + const auto session = &_item->history()->session(); + const auto transcribes = &session->api().transcribes(); + if (session->premium() + || transcribes->freeFor(_item) + || transcribes->trialsCount()) { return false; } - if (_item->history()->session().api().transcribes().trialsCount()) { - return false; - } - const auto until = _item->history()->session().api().transcribes() - .trialsRefreshAt(); + const auto until = transcribes->trialsRefreshAt(); if (!until || base::unixtime::now() >= until) { return false; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index b155b1d29..d884580d5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -439,10 +439,13 @@ QSize Document::countOptimalSize() { auto hasTranscribe = false; const auto voice = Get(); if (voice) { - const auto session = &_realParent->history()->session(); + const auto history = _realParent->history(); + const auto session = &history->session(); + const auto transcribes = &session->api().transcribes(); if (_parent->data()->media()->ttlSeconds() || (!session->premium() - && !session->api().transcribes().trialsSupport())) { + && !transcribes->freeFor(_realParent) + && !transcribes->trialsSupport())) { voice->transcribe = nullptr; voice->transcribeText = {}; } else { @@ -452,8 +455,7 @@ QSize Document::countOptimalSize() { _realParent, false); } - const auto &entry = session->api().transcribes().entry( - _realParent); + const auto &entry = transcribes->entry(_realParent); const auto update = [=] { repaint(); }; voice->transcribe->setLoading( entry.shown && (entry.requestId || entry.pending), From 964846f1bb35f8017897b1e5389c0c39278f2644 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 9 Feb 2024 18:19:40 +0400 Subject: [PATCH 32/73] Add stories rights to group admins edit. --- .../boxes/peers/edit_peer_permissions_box.cpp | 21 ++++++++++++++----- Telegram/SourceFiles/data/data_channel.cpp | 9 -------- .../history/history_inner_widget.cpp | 1 + 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index 9b0747870..385cb1ac6 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -104,7 +104,7 @@ constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000); using Flag = ChatAdminRight; if (options.isGroup) { - auto result = std::vector{ + auto first = std::vector{ { Flag::ChangeInfo, tr::lng_rights_group_info(tr::now) }, { Flag::DeleteMessages, tr::lng_rights_group_delete(tr::now) }, { Flag::BanUsers, tr::lng_rights_group_ban(tr::now) }, @@ -113,19 +113,30 @@ constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000); : tr::lng_rights_group_invite(tr::now) }, { Flag::ManageTopics, tr::lng_rights_group_topics(tr::now) }, { Flag::PinMessages, tr::lng_rights_group_pin(tr::now) }, + }; + auto stories = std::vector{ + { Flag::PostStories, tr::lng_rights_channel_post_stories(tr::now) }, + { Flag::EditStories, tr::lng_rights_channel_edit_stories(tr::now) }, + { Flag::DeleteStories, tr::lng_rights_channel_delete_stories(tr::now) }, + }; + auto second = std::vector{ { Flag::ManageCall, tr::lng_rights_group_manage_calls(tr::now) }, { Flag::Anonymous, tr::lng_rights_group_anonymous(tr::now) }, { Flag::AddAdmins, tr::lng_rights_add_admins(tr::now) }, }; if (!options.isForum) { - result.erase( + first.erase( ranges::remove( - result, + first, Flag::ManageTopics | Flag(), &AdminRightLabel::flags), - end(result)); + end(first)); } - return { { std::nullopt, std::move(result) } }; + return { + { std::nullopt, std::move(first) }, + { tr::lng_rights_channel_manage_stories(), std::move(stories) }, + { std::nullopt, std::move(second) }, + }; } auto first = std::vector{ { Flag::ChangeInfo, tr::lng_rights_channel_info(tr::now) }, diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 87d2a5301..469351af2 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -556,25 +556,16 @@ bool ChannelData::canDeleteMessages() const { } bool ChannelData::canPostStories() const { - if (!isBroadcast()) { - return false; - } return amCreator() || (adminRights() & AdminRight::PostStories); } bool ChannelData::canEditStories() const { - if (!isBroadcast()) { - return false; - } return amCreator() || (adminRights() & AdminRight::EditStories); } bool ChannelData::canDeleteStories() const { - if (!isBroadcast()) { - return false; - } return amCreator() || (adminRights() & AdminRight::DeleteStories); } diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index b7955f4ba..b85703cf0 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -355,6 +355,7 @@ HistoryInner::HistoryInner( _theme = std::move(theme); controller->setChatStyleTheme(_theme); }, lifetime()); + Assert(_theme != nullptr); setAttribute(Qt::WA_AcceptTouchEvents); From 11f084729556cad68bc4949df2740409b41fb03b Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 12 Feb 2024 12:45:37 +0400 Subject: [PATCH 33/73] Update API scheme on layer 174. --- Telegram/SourceFiles/data/data_histories.cpp | 8 +++----- Telegram/SourceFiles/data/data_msg_id.h | 2 +- Telegram/SourceFiles/history/history_item_components.cpp | 6 +++--- Telegram/SourceFiles/history/history_item_helpers.cpp | 2 +- Telegram/SourceFiles/mtproto/scheme/api.tl | 6 +++--- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index 34bc7b6d2..0dde157ad 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -40,11 +40,9 @@ MTPInputReplyTo ReplyToForMTP( const auto owner = &history->owner(); if (replyTo.storyId) { if (const auto peer = owner->peerLoaded(replyTo.storyId.peer)) { - if (const auto user = peer->asUser()) { - return MTP_inputReplyToStory( - user->inputUser, - MTP_int(replyTo.storyId.story)); - } + return MTP_inputReplyToStory( + peer->input, + MTP_int(replyTo.storyId.story)); } } else if (replyTo.messageId || replyTo.topicRootId) { const auto to = LookupReplyTo(history, replyTo.messageId); diff --git a/Telegram/SourceFiles/data/data_msg_id.h b/Telegram/SourceFiles/data/data_msg_id.h index 4555bda3b..d2790a288 100644 --- a/Telegram/SourceFiles/data/data_msg_id.h +++ b/Telegram/SourceFiles/data/data_msg_id.h @@ -164,7 +164,7 @@ struct FullReplyTo { int quoteOffset = 0; [[nodiscard]] bool valid() const { - return messageId || (storyId && peerIsUser(storyId.peer)); + return messageId || (storyId && storyId.peer); } explicit operator bool() const { return valid(); diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 26fca0b19..ae6d3dc3a 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -330,7 +330,7 @@ ReplyFields ReplyFieldsFromMTP( return result; }, [&](const MTPDmessageReplyStoryHeader &data) { return ReplyFields{ - .externalPeerId = peerFromUser(data.vuser_id()), + .externalPeerId = peerFromMTP(data.vpeer()), .storyId = data.vstory_id().v, }; }); @@ -362,9 +362,9 @@ FullReplyTo ReplyToFromMTP( result.quoteOffset = data.vquote_offset().value_or_empty(); return result; }, [&](const MTPDinputReplyToStory &data) { - if (const auto parsed = Data::UserFromInputMTP( + if (const auto parsed = Data::PeerFromInputMTP( &history->owner(), - data.vuser_id())) { + data.vpeer())) { return FullReplyTo{ .storyId = { parsed->id, data.vstory_id().v }, }; diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 911ec9068..752560a7f 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -416,7 +416,7 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) { if (const auto replyTo = action.replyTo) { if (replyTo.storyId) { return MTP_messageReplyStoryHeader( - MTP_long(peerToUser(replyTo.storyId.peer).bare), + peerToMTP(replyTo.storyId.peer), MTP_int(replyTo.storyId.story)); } using Flag = MTPDmessageReplyHeader::Flag; diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index a1ebf13f6..1f17c1a7d 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -1277,7 +1277,7 @@ messages.messageViews#b6c4f543 views:Vector chats:Vector use messages.discussionMessage#a6341782 flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector users:Vector = messages.DiscussionMessage; messageReplyHeader#afbc09db flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true quote:flags.9?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_from:flags.5?MessageFwdHeader reply_media:flags.8?MessageMedia reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector quote_offset:flags.10?int = MessageReplyHeader; -messageReplyStoryHeader#9c98bfc1 user_id:long story_id:int = MessageReplyHeader; +messageReplyStoryHeader#e5af939 peer:Peer story_id:int = MessageReplyHeader; messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies; @@ -1556,7 +1556,7 @@ storyViews#8d595cd6 flags:# has_viewers:flags.1?true views_count:int forwards_co storyItemDeleted#51e6ee4f id:int = StoryItem; storyItemSkipped#ffadc913 flags:# close_friends:flags.8?true id:int date:int expire_date:int = StoryItem; -storyItem#af6365a1 flags:# pinned:flags.5?true public:flags.7?true close_friends:flags.8?true min:flags.9?true noforwards:flags.10?true edited:flags.11?true contacts:flags.12?true selected_contacts:flags.13?true out:flags.16?true id:int date:int fwd_from:flags.17?StoryFwdHeader expire_date:int caption:flags.0?string entities:flags.1?Vector media:MessageMedia media_areas:flags.14?Vector privacy:flags.2?Vector views:flags.3?StoryViews sent_reaction:flags.15?Reaction = StoryItem; +storyItem#79b26a24 flags:# pinned:flags.5?true public:flags.7?true close_friends:flags.8?true min:flags.9?true noforwards:flags.10?true edited:flags.11?true contacts:flags.12?true selected_contacts:flags.13?true out:flags.16?true id:int date:int from_id:flags.18?Peer fwd_from:flags.17?StoryFwdHeader expire_date:int caption:flags.0?string entities:flags.1?Vector media:MessageMedia media_areas:flags.14?Vector privacy:flags.2?Vector views:flags.3?StoryViews sent_reaction:flags.15?Reaction = StoryItem; stories.allStoriesNotModified#1158fe3e flags:# state:string stealth_mode:StoriesStealthMode = stories.AllStories; stories.allStories#6efc5e81 flags:# has_more:flags.0?true count:int state:string peer_stories:Vector chats:Vector users:Vector stealth_mode:StoriesStealthMode = stories.AllStories; @@ -1572,7 +1572,7 @@ stories.storyViewsList#59d78fc5 flags:# count:int views_count:int forwards_count stories.storyViews#de9eed1d views:Vector users:Vector = stories.StoryViews; inputReplyToMessage#22c0f6d5 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector quote_offset:flags.4?int = InputReplyTo; -inputReplyToStory#15b0f283 user_id:InputUser story_id:int = InputReplyTo; +inputReplyToStory#5881323a peer:InputPeer story_id:int = InputReplyTo; exportedStoryLink#3fc9053b link:string = ExportedStoryLink; From f674ace805b19fccc6d75d3268ee4eb75793fcbd Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 12 Feb 2024 13:15:08 +0400 Subject: [PATCH 34/73] Send comments to group stories. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/dialogs/dialogs_row.cpp | 2 +- .../history_view_compose_controls.cpp | 44 ++++++++++ .../controls/history_view_compose_controls.h | 5 ++ .../view/history_view_replies_section.cpp | 45 +---------- .../stories/media_stories_controller.cpp | 14 ++-- .../media/stories/media_stories_controller.h | 2 +- .../media/stories/media_stories_reactions.cpp | 8 +- .../media/stories/media_stories_reactions.h | 4 +- .../stories/media_stories_recent_views.cpp | 2 +- .../media/stories/media_stories_reply.cpp | 80 +++++++++++++++---- .../media/stories/media_stories_reply.h | 7 +- 12 files changed, 140 insertions(+), 74 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 29f186057..2cac04b85 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2611,6 +2611,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_broadcast_silent_ph" = "Silent broadcast..."; "lng_send_anonymous_ph" = "Send anonymously..."; "lng_story_reply_ph" = "Reply privately..."; +"lng_story_comment_ph" = "Comment story..."; "lng_send_text_no" = "Text not allowed."; "lng_send_text_no_about" = "The admins of this group only allow sending {types}."; "lng_send_text_type_and_last" = "{types} and {last}"; diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp index 29f849956..9ecddf88f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -448,7 +448,7 @@ void Row::paintUserpic( ? _cornerBadgeShown : !_cornerBadgeUserpic->layersManager.isDisplayedNone(); const auto storiesPeer = peer - ? ((peer->isUser() || peer->isBroadcast()) ? peer : nullptr) + ? ((peer->isUser() || peer->isChannel()) ? peer : nullptr) : nullptr; const auto storiesFolder = peer ? nullptr : _id.folder(); const auto storiesHas = storiesPeer diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index cc960fe9b..c7340489b 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "base/platform/base_platform_info.h" #include "base/qt_signal_producer.h" +#include "base/timer_rpl.h" #include "base/unixtime.h" #include "boxes/edit_caption_box.h" #include "chat_helpers/compose/compose_show.h" @@ -95,6 +96,7 @@ constexpr auto kMouseEvents = { QEvent::MouseButtonPress, QEvent::MouseButtonRelease }; +constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200); constexpr auto kCommonModifiers = 0 | Qt::ShiftModifier @@ -3343,4 +3345,46 @@ void ComposeControls::checkCharsLimitation() { } } +rpl::producer SlowmodeSecondsLeft(not_null peer) { + return peer->session().changes().peerFlagsValue( + peer, + Data::PeerUpdate::Flag::Slowmode + ) | rpl::map([=] { + return peer->slowmodeSecondsLeft(); + }) | rpl::map([=](int delay) -> rpl::producer { + auto start = rpl::single(delay); + if (!delay) { + return start; + } + return std::move( + start + ) | rpl::then(base::timer_each( + kRefreshSlowmodeLabelTimeout + ) | rpl::map([=] { + return peer->slowmodeSecondsLeft(); + }) | rpl::take_while([=](int delay) { + return delay > 0; + })) | rpl::then(rpl::single(0)); + }) | rpl::flatten_latest(); +} + +rpl::producer SendDisabledBySlowmode(not_null peer) { + const auto history = peer->owner().history(peer); + auto hasSendingMessage = peer->session().changes().historyFlagsValue( + history, + Data::HistoryUpdate::Flag::ClientSideMessages + ) | rpl::map([=] { + return history->latestSendingMessage() != nullptr; + }) | rpl::distinct_until_changed(); + + using namespace rpl::mappers; + const auto channel = peer->asChannel(); + return (!channel || channel->amCreator()) + ? (rpl::single(false) | rpl::type_erased()) + : rpl::combine( + channel->slowmodeAppliedValue(), + std::move(hasSendingMessage), + _1 && _2); +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 4911bfde9..b1bc012ca 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -439,4 +439,9 @@ private: }; +[[nodiscard]] rpl::producer SlowmodeSecondsLeft( + not_null peer); +[[nodiscard]] rpl::producer SendDisabledBySlowmode( + not_null peer); + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 1043cd596..a599e638e 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -99,8 +99,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { namespace { -constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200); - rpl::producer RootViewContent( not_null history, MsgId rootId, @@ -631,45 +629,6 @@ bool RepliesWidget::computeAreComments() const { } void RepliesWidget::setupComposeControls() { - auto slowmodeSecondsLeft = session().changes().peerFlagsValue( - _history->peer, - Data::PeerUpdate::Flag::Slowmode - ) | rpl::map([=] { - return _history->peer->slowmodeSecondsLeft(); - }) | rpl::map([=](int delay) -> rpl::producer { - auto start = rpl::single(delay); - if (!delay) { - return start; - } - return std::move( - start - ) | rpl::then(base::timer_each( - kRefreshSlowmodeLabelTimeout - ) | rpl::map([=] { - return _history->peer->slowmodeSecondsLeft(); - }) | rpl::take_while([=](int delay) { - return delay > 0; - })) | rpl::then(rpl::single(0)); - }) | rpl::flatten_latest(); - - const auto channel = _history->peer->asChannel(); - Assert(channel != nullptr); - - auto hasSendingMessage = session().changes().historyFlagsValue( - _history, - Data::HistoryUpdate::Flag::ClientSideMessages - ) | rpl::map([=] { - return _history->latestSendingMessage() != nullptr; - }) | rpl::distinct_until_changed(); - - using namespace rpl::mappers; - auto sendDisabledBySlowmode = (!channel || channel->amCreator()) - ? (rpl::single(false) | rpl::type_erased()) - : rpl::combine( - channel->slowmodeAppliedValue(), - std::move(hasSendingMessage), - _1 && _2); - auto topicWriteRestrictions = rpl::single( ) | rpl::then(session().changes().topicUpdates( Data::TopicUpdate::Flag::Closed @@ -719,8 +678,8 @@ void RepliesWidget::setupComposeControls() { .topicRootId = _topic ? _topic->rootId() : MsgId(0), .showSlowmodeError = [=] { return showSlowmodeError(); }, .sendActionFactory = [=] { return prepareSendAction({}); }, - .slowmodeSecondsLeft = std::move(slowmodeSecondsLeft), - .sendDisabledBySlowmode = std::move(sendDisabledBySlowmode), + .slowmodeSecondsLeft = SlowmodeSecondsLeft(_history->peer), + .sendDisabledBySlowmode = SendDisabledBySlowmode(_history->peer), .writeRestriction = std::move(writeRestriction), }); diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 76e880664..7814b71d2 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -299,7 +299,9 @@ Controller::Controller(not_null delegate) _reactions->chosen( ) | rpl::start_with_next([=](Reactions::Chosen chosen) { - reactionChosen(chosen.mode, chosen.reaction); + if (reactionChosen(chosen.mode, chosen.reaction)) { + _reactions->animateAndProcess(std::move(chosen)); + } }, _lifetime); _delegate->storiesLayerShown( @@ -628,13 +630,15 @@ void Controller::toggleLiked() { _reactions->toggleLiked(); } -void Controller::reactionChosen(ReactionsMode mode, ChosenReaction chosen) { +bool Controller::reactionChosen(ReactionsMode mode, ChosenReaction chosen) { + auto result = true; if (mode == ReactionsMode::Message) { - _replyArea->sendReaction(chosen.id); + result = _replyArea->sendReaction(chosen.id); } else if (const auto peer = shownPeer()) { peer->owner().stories().sendReaction(_shown, chosen.id); } unfocusReply(); + return result; } void Controller::showFullCaption() { @@ -907,7 +911,7 @@ void Controller::show( .views = story->views(), .total = story->interactions(), .type = RecentViewsTypeFor(peer), - .canViewReactions = CanViewReactionsFor(peer), + .canViewReactions = CanViewReactionsFor(peer) && !peer->isMegagroup(), }, _reactions->likedValue()); if (const auto nowLikeButton = _recentViews->likeButton()) { if (wasLikeButton != nowLikeButton) { @@ -1007,7 +1011,7 @@ void Controller::subscribeToSession() { .views = update.story->views(), .total = update.story->interactions(), .type = RecentViewsTypeFor(peer), - .canViewReactions = CanViewReactionsFor(peer), + .canViewReactions = CanViewReactionsFor(peer) && !peer->isMegagroup(), }); updateAreas(update.story); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 0a6c4a8fe..b8ad1e20b 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -260,7 +260,7 @@ private: [[nodiscard]] int repostSkipTop() const; void updateAreas(Data::Story *story); - void reactionChosen(ReactionsMode mode, ChosenReaction chosen); + bool reactionChosen(ReactionsMode mode, ChosenReaction chosen); const not_null _delegate; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index bea75eb90..f401f6b37 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -793,7 +793,7 @@ Reactions::Reactions(not_null controller) : _controller(controller) , _panel(std::make_unique(_controller)) { _panel->chosen() | rpl::start_with_next([=](Chosen &&chosen) { - animateAndProcess(std::move(chosen)); + _chosen.fire(std::move(chosen)); }, _lifetime); } @@ -887,7 +887,7 @@ auto Reactions::attachToMenu( selector->chosen() | rpl::start_with_next([=](ChosenReaction reaction) { menu->hideMenu(); - animateAndProcess({ reaction, ReactionsMode::Reaction }); + _chosen.fire({ reaction, ReactionsMode::Reaction }); }, selector->lifetime()); return AttachSelectorResult::Attached; @@ -933,7 +933,7 @@ void Reactions::toggleLiked() { void Reactions::applyLike(Data::ReactionId id) { if (_liked.current() != id) { - animateAndProcess({ { .id = id }, ReactionsMode::Reaction }); + _chosen.fire({ { .id = id }, ReactionsMode::Reaction }); } } @@ -971,8 +971,6 @@ void Reactions::animateAndProcess(Chosen &&chosen) { .scaleOutTarget = scaleOutTarget, }, target, std::move(done)); } - - _chosen.fire(std::move(chosen)); } void Reactions::assignLikedId(Data::ReactionId id) { diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.h b/Telegram/SourceFiles/media/stories/media_stories_reactions.h index d27761f46..de9f0e0ce 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.h @@ -87,6 +87,8 @@ public: void attachToReactionButton(not_null button); void setReactionIconWidget(Ui::RpWidget *widget); + void animateAndProcess(Chosen &&chosen); + using AttachStripResult = HistoryView::Reactions::AttachSelectorResult; [[nodiscard]] AttachStripResult attachToMenu( not_null menu, @@ -95,8 +97,6 @@ public: private: class Panel; - void animateAndProcess(Chosen &&chosen); - void assignLikedId(Data::ReactionId id); [[nodiscard]] Fn setLikedIdIconInit( not_null owner, diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp index 442199300..e6b53d277 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp @@ -139,7 +139,7 @@ constexpr auto kLoadViewsPages = 2; RecentViewsType RecentViewsTypeFor(not_null peer) { return peer->isSelf() ? RecentViewsType::Self - : peer->isChannel() + : peer->isBroadcast() ? RecentViewsType::Channel : peer->isServiceUser() ? RecentViewsType::Changelog diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index dba4cb02b..d6959d1a6 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_account.h" #include "storage/storage_media_prepare.h" #include "ui/chat/attach/attach_prepare.h" +#include "ui/text/format_values.h" #include "ui/round_rect.h" #include "window/section_widget.h" #include "styles/style_boxes.h" // sendMediaPreviewSize. @@ -49,11 +50,15 @@ namespace Media::Stories { namespace { [[nodiscard]] rpl::producer PlaceholderText( - const std::shared_ptr &show) { - return show->session().data().stories().stealthModeValue( - ) | rpl::map([](Data::StealthMode value) { - return value.enabledTill; - }) | rpl::distinct_until_changed() | rpl::map([](TimeId till) { + const std::shared_ptr &show, + rpl::producer isComment) { + return rpl::combine( + show->session().data().stories().stealthModeValue(), + std::move(isComment) + ) | rpl::map([](Data::StealthMode value, bool isComment) { + return std::tuple(value.enabledTill, isComment); + }) | rpl::distinct_until_changed( + ) | rpl::map([](TimeId till, bool isComment) { return rpl::single( rpl::empty ) | rpl::then( @@ -64,11 +69,13 @@ namespace { return left > 0; }) | rpl::then( rpl::single(0) - ) | rpl::map([](TimeId left) { + ) | rpl::map([=](TimeId left) { return left ? tr::lng_stealth_mode_countdown( lt_left, rpl::single(TimeLeftText(left))) + : isComment + ? tr::lng_story_comment_ph() : tr::lng_story_reply_ph(); }) | rpl::flatten_latest(); }) | rpl::flatten_latest(); @@ -118,7 +125,9 @@ ReplyArea::ReplyArea(not_null controller) .mode = HistoryView::ComposeControlsMode::Normal, .sendMenuType = SendMenu::Type::SilentOnly, .stickerOrEmojiChosen = _controller->stickerOrEmojiChosen(), - .customPlaceholder = PlaceholderText(_controller->uiShow()), + .customPlaceholder = PlaceholderText( + _controller->uiShow(), + rpl::deferred([=] { return _isComment.value(); })), .voiceCustomCancelText = tr::lng_record_cancel_stories(tr::now), .voiceLockFromBottom = true, .features = { @@ -171,7 +180,7 @@ void ReplyArea::initGeometry() { }, _lifetime); } -void ReplyArea::sendReaction(const Data::ReactionId &id) { +bool ReplyArea::sendReaction(const Data::ReactionId &id) { Expects(_data.peer != nullptr); auto message = Api::MessageToSend(prepareSendAction({})); @@ -188,9 +197,8 @@ void ReplyArea::sendReaction(const Data::ReactionId &id) { }; } } - if (!message.textWithTags.empty()) { - send(std::move(message), {}, true); - } + return !message.textWithTags.empty() + && send(std::move(message), {}, true); } void ReplyArea::send(Api::SendOptions options) { @@ -203,10 +211,14 @@ void ReplyArea::send(Api::SendOptions options) { send(std::move(message), options); } -void ReplyArea::send( +bool ReplyArea::send( Api::MessageToSend message, Api::SendOptions options, bool skipToast) { + if (!options.scheduled && showSlowmodeError()) { + return false; + } + const auto error = GetErrorTextForSending( _data.peer, { @@ -216,12 +228,14 @@ void ReplyArea::send( }); if (!error.isEmpty()) { _controller->uiShow()->showToast(error); + return false; } session().api().sendMessage(std::move(message)); finishSending(skipToast); _controls->clear(); + return true; } void ReplyArea::sendVoice(VoiceToSend &&data) { @@ -249,7 +263,8 @@ bool ReplyArea::sendExistingDocument( if (error) { show->showToast(*error); return false; - } else if (Window::ShowSendPremiumError(show, document)) { + } else if (showSlowmodeError() + || Window::ShowSendPremiumError(show, document)) { return false; } @@ -279,6 +294,8 @@ bool ReplyArea::sendExistingPhoto( if (error) { show->showToast(*error); return false; + } else if (showSlowmodeError()) { + return false; } Api::SendExistingPhoto( @@ -409,6 +426,8 @@ void ReplyArea::chooseAttach( if (const auto error = Data::AnyFileRestrictionError(peer)) { _controller->uiShow()->showToast(*error); return; + } else if (showSlowmodeError()) { + return; } const auto filter = (overrideSendImagesAsPhotos == true) @@ -666,6 +685,7 @@ void ReplyArea::show( const auto peer = data.peer; const auto history = peer ? peer->owner().history(peer).get() : nullptr; const auto user = peer->asUser(); + _isComment = peer->isMegagroup(); auto writeRestriction = Data::CanSendAnythingValue( peer ) | rpl::map([=](bool can) { @@ -681,8 +701,13 @@ void ReplyArea::show( .type = WriteRestrictionType::PremiumRequired, }; }); + using namespace HistoryView; _controls->setHistory({ .history = history, + .showSlowmodeError = [=] { return showSlowmodeError(); }, + .sendActionFactory = [=] { return prepareSendAction({}); }, + .slowmodeSecondsLeft = SlowmodeSecondsLeft(history->peer), + .sendDisabledBySlowmode = SendDisabledBySlowmode(history->peer), .liked = std::move( likedValue ) | rpl::map([](const Data::ReactionId &id) { @@ -692,7 +717,7 @@ void ReplyArea::show( }); _controls->clear(); const auto hidden = peer - && (!peer->isUser() || peer->isSelf() || peer->isServiceUser()); + && (peer->isBroadcast() || peer->isSelf() || peer->isServiceUser()); const auto cant = !peer; if (!hidden && !cant) { _controls->show(); @@ -714,6 +739,33 @@ void ReplyArea::show( } } +bool ReplyArea::showSlowmodeError() { + const auto text = [&] { + const auto story = _controller->story(); + if (!story) { + return QString(); + } + const auto peer = story->peer(); + if (const auto left = peer->slowmodeSecondsLeft()) { + return tr::lng_slowmode_enabled( + tr::now, + lt_left, + Ui::FormatDurationWordsSlowmode(left)); + } else if (peer->slowmodeApplied()) { + const auto history = peer->owner().history(peer); + if (const auto item = history->latestSendingMessage()) { + return tr::lng_slowmode_no_many(tr::now); + } + } + return QString(); + }(); + if (text.isEmpty()) { + return false; + } + _controller->uiShow()->showToast(text); + return true; +} + Main::Session &ReplyArea::session() const { Expects(_data.peer != nullptr); diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.h b/Telegram/SourceFiles/media/stories/media_stories_reply.h index bb5a58e70..90214b421 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.h @@ -64,7 +64,7 @@ public: void show( ReplyAreaData data, rpl::producer likedValue); - void sendReaction(const Data::ReactionId &id); + bool sendReaction(const Data::ReactionId &id); [[nodiscard]] bool focused() const; [[nodiscard]] rpl::producer focusedValue() const; @@ -84,7 +84,7 @@ private: [[nodiscard]] Main::Session &session() const; [[nodiscard]] not_null history() const; - void send( + bool send( Api::MessageToSend message, Api::SendOptions options, bool skipToast = false); @@ -142,8 +142,11 @@ private: void chooseAttach(std::optional overrideSendImagesAsPhotos); void showPremiumToast(not_null emoji); + [[nodiscard]] bool showSlowmodeError(); const not_null _controller; + rpl::variable _isComment; + const std::unique_ptr _controls; std::unique_ptr _cant; From 0fd8ceca6b7897946d899c61ae178bd83b7a55be Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 12 Feb 2024 14:51:05 +0400 Subject: [PATCH 35/73] Use Ui::DynamicImage and ui/dynamic_thumbnails module. --- Telegram/CMakeLists.txt | 2 + .../data/stickers/data_custom_emoji.cpp | 40 +++ .../data/stickers/data_custom_emoji.h | 8 + .../dialogs/dialogs_inner_widget.cpp | 1 - .../dialogs/ui/dialogs_stories_content.cpp | 303 +--------------- .../dialogs/ui/dialogs_stories_content.h | 6 - .../dialogs/ui/dialogs_stories_list.cpp | 1 + .../dialogs/ui/dialogs_stories_list.h | 12 +- .../view/media/history_view_giveaway.cpp | 6 +- .../view/media/history_view_giveaway.h | 8 +- .../media/history_view_similar_channels.cpp | 8 +- .../media/history_view_similar_channels.h | 10 +- .../view/media/history_view_story_mention.cpp | 9 +- .../view/media/history_view_story_mention.h | 10 +- Telegram/SourceFiles/info/info_top_bar.cpp | 3 - .../SourceFiles/ui/dynamic_thumbnails.cpp | 322 ++++++++++++++++++ Telegram/SourceFiles/ui/dynamic_thumbnails.h | 25 ++ Telegram/lib_ui | 2 +- 18 files changed, 429 insertions(+), 347 deletions(-) create mode 100644 Telegram/SourceFiles/ui/dynamic_thumbnails.cpp create mode 100644 Telegram/SourceFiles/ui/dynamic_thumbnails.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index ac17eb8a5..e1766f5e6 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1417,6 +1417,8 @@ PRIVATE ui/widgets/level_meter.h ui/countryinput.cpp ui/countryinput.h + ui/dynamic_thumbnails.cpp + ui/dynamic_thumbnails.h ui/filter_icons.cpp ui/filter_icons.h ui/filter_icon_panel.cpp diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 0e40b7792..1e225afc5 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -19,14 +19,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer.h" #include "data/data_message_reactions.h" #include "data/stickers/data_stickers.h" +#include "dialogs/ui/dialogs_stories_content.h" +#include "dialogs/ui/dialogs_stories_content.h" #include "lottie/lottie_common.h" #include "lottie/lottie_frame_generator.h" #include "ffmpeg/ffmpeg_frame_generator.h" #include "chat_helpers/stickers_lottie.h" #include "storage/file_download.h" // kMaxFileInMemory #include "ui/widgets/fields/input_field.h" +#include "ui/text/custom_emoji_instance.h" #include "ui/text/text_custom_emoji.h" #include "ui/text/text_utilities.h" +#include "ui/dynamic_thumbnails.h" #include "ui/ui_utility.h" #include "apiwrap.h" #include "styles/style_chat.h" @@ -94,6 +98,10 @@ private: return u"internal:"_q; } +[[nodiscard]] QString UserpicEmojiPrefix() { + return u"userpic:"_q; +} + [[nodiscard]] QString InternalPadding(QMargins value) { return value.isNull() ? QString() : QString(",%1,%2,%3,%4" ).arg(value.left() @@ -528,6 +536,10 @@ std::unique_ptr CustomEmojiManager::create( int sizeOverride) { if (data.startsWith(InternalPrefix())) { return internal(data); + } else if (data.startsWith(UserpicEmojiPrefix())) { + const auto ratio = style::DevicePixelRatio(); + const auto size = FrameSizeFromTag(tag, sizeOverride) / ratio; + return userpic(data, std::move(update), size); } const auto parsed = ParseCustomEmojiData(data); return parsed @@ -575,6 +587,26 @@ std::unique_ptr CustomEmojiManager::internal( info.textColor); } +std::unique_ptr CustomEmojiManager::userpic( + QStringView data, + Fn update, + int size) { + const auto v = data.mid(UserpicEmojiPrefix().size()).split(','); + if (v.size() != 5 && v.size() != 1) { + return nullptr; + } + const auto id = PeerId(v[0].toULongLong()); + const auto padding = (v.size() == 5) + ? QMargins(v[1].toInt(), v[2].toInt(), v[3].toInt(), v[4].toInt()) + : QMargins(); + return std::make_unique( + data.toString(), + Ui::MakeUserpicThumbnail(_owner->peer(id)), + std::move(update), + padding, + size); +} + void CustomEmojiManager::resolve( QStringView data, not_null listener) { @@ -955,6 +987,14 @@ QString CustomEmojiManager::registerInternalEmoji( return result + InternalPadding(padding); } +[[nodiscard]] QString CustomEmojiManager::peerUserpicEmojiData( + not_null peer, + QMargins padding) { + return UserpicEmojiPrefix() + + QString::number(peer->id.value) + + InternalPadding(padding); +} + int FrameSizeFromTag(SizeTag tag) { const auto emoji = EmojiSizeFromTag(tag); const auto factor = style::DevicePixelRatio(); diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h index 49799e639..1cbbe0de9 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h @@ -92,6 +92,10 @@ public: QMargins padding = {}, bool textColor = true); + [[nodiscard]] QString peerUserpicEmojiData( + not_null peer, + QMargins padding = {}); + [[nodiscard]] uint64 coloredSetId() const; private: @@ -146,6 +150,10 @@ private: LoaderFactory factory); [[nodiscard]] std::unique_ptr internal( QStringView data); + [[nodiscard]] std::unique_ptr userpic( + QStringView data, + Fn update, + int size); [[nodiscard]] static int SizeIndex(SizeTag tag); const not_null _owner; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 3047c27e2..dab3e303a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_three_state_icon.h" #include "dialogs/ui/dialogs_layout.h" #include "dialogs/ui/dialogs_stories_content.h" -#include "dialogs/ui/dialogs_stories_list.h" #include "dialogs/ui/dialogs_video_userpic.h" #include "dialogs/dialogs_indexed_list.h" #include "dialogs/dialogs_widget.h" diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp index 25f996620..bf60a2488 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp @@ -22,6 +22,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/info_memento.h" #include "main/main_session.h" #include "lang/lang_keys.h" +#include "ui/dynamic_image.h" +#include "ui/dynamic_thumbnails.h" #include "ui/painter.h" #include "window/window_session_controller.h" #include "styles/style_menu_icons.h" @@ -31,99 +33,6 @@ namespace { constexpr auto kShownLastCount = 3; -class PeerUserpic final : public Thumbnail { -public: - explicit PeerUserpic(not_null peer); - - QImage image(int size) override; - void subscribeToUpdates(Fn callback) override; - -private: - struct Subscribed { - explicit Subscribed(Fn callback) - : callback(std::move(callback)) { - } - - Ui::PeerUserpicView view; - Fn callback; - InMemoryKey key; - rpl::lifetime photoLifetime; - rpl::lifetime downloadLifetime; - }; - - [[nodiscard]] bool waitingUserpicLoad() const; - void processNewPhoto(); - - const not_null _peer; - QImage _frame; - std::unique_ptr _subscribed; - -}; - -class StoryThumbnail : public Thumbnail { -public: - explicit StoryThumbnail(FullStoryId id); - virtual ~StoryThumbnail() = default; - - QImage image(int size) override; - void subscribeToUpdates(Fn callback) override; - -protected: - struct Thumb { - Image *image = nullptr; - bool blurred = false; - }; - [[nodiscard]] virtual Main::Session &session() = 0; - [[nodiscard]] virtual Thumb loaded(FullStoryId id) = 0; - virtual void clear() = 0; - -private: - const FullStoryId _id; - QImage _full; - rpl::lifetime _subscription; - QImage _prepared; - bool _blurred = false; - -}; - -class PhotoThumbnail final : public StoryThumbnail { -public: - PhotoThumbnail(not_null photo, FullStoryId id); - -private: - Main::Session &session() override; - Thumb loaded(FullStoryId id) override; - void clear() override; - - const not_null _photo; - std::shared_ptr _media; - -}; - -class VideoThumbnail final : public StoryThumbnail { -public: - VideoThumbnail(not_null video, FullStoryId id); - -private: - Main::Session &session() override; - Thumb loaded(FullStoryId id) override; - void clear() override; - - const not_null _video; - std::shared_ptr _media; - -}; - -class EmptyThumbnail final : public Thumbnail { -public: - QImage image(int size) override; - void subscribeToUpdates(Fn callback) override; - -private: - QImage _cached; - -}; - class State final { public: State(not_null data, Data::StorySourcesList list); @@ -135,193 +44,10 @@ private: const Data::StorySourcesList _list; base::flat_map< not_null, - std::shared_ptr> _userpics; + std::shared_ptr> _userpics; }; -PeerUserpic::PeerUserpic(not_null peer) -: _peer(peer) { -} - -QImage PeerUserpic::image(int size) { - Expects(_subscribed != nullptr); - - const auto good = (_frame.width() == size * _frame.devicePixelRatio()); - const auto key = _peer->userpicUniqueKey(_subscribed->view); - if (!good || (_subscribed->key != key && !waitingUserpicLoad())) { - const auto ratio = style::DevicePixelRatio(); - _subscribed->key = key; - _frame = QImage( - QSize(size, size) * ratio, - QImage::Format_ARGB32_Premultiplied); - _frame.setDevicePixelRatio(ratio); - _frame.fill(Qt::transparent); - - auto p = Painter(&_frame); - _peer->paintUserpic(p, _subscribed->view, 0, 0, size); - } - return _frame; -} - -bool PeerUserpic::waitingUserpicLoad() const { - return _peer->hasUserpic() && _peer->useEmptyUserpic(_subscribed->view); -} - -void PeerUserpic::subscribeToUpdates(Fn callback) { - if (!callback) { - _subscribed = nullptr; - return; - } - _subscribed = std::make_unique(std::move(callback)); - - _peer->session().changes().peerUpdates( - _peer, - Data::PeerUpdate::Flag::Photo - ) | rpl::start_with_next([=] { - _subscribed->callback(); - processNewPhoto(); - }, _subscribed->photoLifetime); - - processNewPhoto(); -} - -void PeerUserpic::processNewPhoto() { - Expects(_subscribed != nullptr); - - if (!waitingUserpicLoad()) { - _subscribed->downloadLifetime.destroy(); - return; - } - _peer->session().downloaderTaskFinished( - ) | rpl::filter([=] { - return !waitingUserpicLoad(); - }) | rpl::start_with_next([=] { - _subscribed->callback(); - _subscribed->downloadLifetime.destroy(); - }, _subscribed->downloadLifetime); -} - -StoryThumbnail::StoryThumbnail(FullStoryId id) -: _id(id) { -} - -QImage StoryThumbnail::image(int size) { - const auto ratio = style::DevicePixelRatio(); - if (_prepared.width() != size * ratio) { - if (_full.isNull()) { - _prepared = QImage( - QSize(size, size) * ratio, - QImage::Format_ARGB32_Premultiplied); - _prepared.fill(Qt::black); - } else { - const auto width = _full.width(); - const auto skip = std::max((_full.height() - width) / 2, 0); - _prepared = _full.copy(0, skip, width, width).scaled( - QSize(size, size) * ratio, - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation); - } - _prepared = Images::Circle(std::move(_prepared)); - _prepared.setDevicePixelRatio(ratio); - } - return _prepared; -} - -void StoryThumbnail::subscribeToUpdates(Fn callback) { - _subscription.destroy(); - if (!callback) { - clear(); - return; - } else if (!_full.isNull() && !_blurred) { - return; - } - const auto thumbnail = loaded(_id); - if (const auto image = thumbnail.image) { - _full = image->original(); - } - _blurred = thumbnail.blurred; - if (!_blurred) { - _prepared = QImage(); - } else { - _subscription = session().downloaderTaskFinished( - ) | rpl::filter([=] { - const auto thumbnail = loaded(_id); - if (!thumbnail.blurred) { - _full = thumbnail.image->original(); - _prepared = QImage(); - _blurred = false; - return true; - } - return false; - }) | rpl::take(1) | rpl::start_with_next(callback); - } -} - -PhotoThumbnail::PhotoThumbnail(not_null photo, FullStoryId id) -: StoryThumbnail(id) -, _photo(photo) { -} - -Main::Session &PhotoThumbnail::session() { - return _photo->session(); -} - -StoryThumbnail::Thumb PhotoThumbnail::loaded(FullStoryId id) { - if (!_media) { - _media = _photo->createMediaView(); - _media->wanted(Data::PhotoSize::Small, id); - } - if (const auto small = _media->image(Data::PhotoSize::Small)) { - return { .image = small }; - } - return { .image = _media->thumbnailInline(), .blurred = true }; -} - -void PhotoThumbnail::clear() { - _media = nullptr; -} - -VideoThumbnail::VideoThumbnail( - not_null video, - FullStoryId id) -: StoryThumbnail(id) -, _video(video) { -} - -Main::Session &VideoThumbnail::session() { - return _video->session(); -} - -StoryThumbnail::Thumb VideoThumbnail::loaded(FullStoryId id) { - if (!_media) { - _media = _video->createMediaView(); - _media->thumbnailWanted(id); - } - if (const auto small = _media->thumbnail()) { - return { .image = small }; - } - return { .image = _media->thumbnailInline(), .blurred = true }; -} - -void VideoThumbnail::clear() { - _media = nullptr; -} - -QImage EmptyThumbnail::image(int size) { - const auto ratio = style::DevicePixelRatio(); - if (_cached.width() != size * ratio) { - _cached = QImage( - QSize(size, size) * ratio, - QImage::Format_ARGB32_Premultiplied); - _cached.fill(Qt::black); - _cached.setDevicePixelRatio(ratio); - } - return _cached; -} - -void EmptyThumbnail::subscribeToUpdates(Fn callback) { -} - State::State(not_null data, Data::StorySourcesList list) : _data(data) , _list(list) { @@ -335,12 +61,12 @@ Content State::next() { const auto source = _data->source(info.id); Assert(source != nullptr); - auto userpic = std::shared_ptr(); + auto userpic = std::shared_ptr(); const auto peer = source->peer; if (const auto i = _userpics.find(peer); i != end(_userpics)) { userpic = i->second; } else { - userpic = MakeUserpicThumbnail(peer); + userpic = Ui::MakeUserpicThumbnail(peer); _userpics.emplace(peer, userpic); } result.elements.push_back({ @@ -430,7 +156,7 @@ rpl::producer LastForPeer(not_null peer) { result.elements.reserve(ids.size()); result.elements.push_back({ .id = uint64(id), - .thumbnail = MakeStoryThumbnail(*maybe), + .thumbnail = Ui::MakeStoryThumbnail(*maybe), .count = 1U, .unreadCount = unread ? 1U : 0U, }); @@ -479,23 +205,6 @@ rpl::producer LastForPeer(not_null peer) { }) | rpl::flatten_latest(); } -std::shared_ptr MakeUserpicThumbnail(not_null peer) { - return std::make_shared(peer); -} - -std::shared_ptr MakeStoryThumbnail( - not_null story) { - using Result = std::shared_ptr; - const auto id = story->fullId(); - return v::match(story->media().data, [](v::null_t) -> Result { - return std::make_shared(); - }, [&](not_null photo) -> Result { - return std::make_shared(photo, id); - }, [&](not_null video) -> Result { - return std::make_shared(video, id); - }); -} - void FillSourceMenu( not_null controller, const ShowMenuRequest &request) { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h index b42715d54..c38854f81 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h @@ -23,7 +23,6 @@ class SessionController; namespace Dialogs::Stories { struct Content; -class Thumbnail; struct ShowMenuRequest; [[nodiscard]] rpl::producer ContentForSession( @@ -32,11 +31,6 @@ struct ShowMenuRequest; [[nodiscard]] rpl::producer LastForPeer(not_null peer); -[[nodiscard]] std::shared_ptr MakeUserpicThumbnail( - not_null peer); -[[nodiscard]] std::shared_ptr MakeStoryThumbnail( - not_null story); - void FillSourceMenu( not_null controller, const ShowMenuRequest &request); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp index e6a93af3a..f615f4fb3 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/tooltip.h" +#include "ui/dynamic_image.h" #include "ui/painter.h" #include "styles/style_dialogs.h" diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h index 71d4aca42..869767745 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h @@ -10,9 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/qt/qt_compare.h" #include "base/timer.h" #include "base/weak_ptr.h" +#include "ui/effects/animations.h" +#include "ui/text/text_custom_emoji.h" #include "ui/widgets/menu/menu_add_action_callback.h" #include "ui/rp_widget.h" -#include "ui/effects/animations.h" class QPainter; @@ -23,22 +24,17 @@ struct DialogsStoriesList; namespace Ui { class PopupMenu; +class DynamicImage; struct OutlineSegment; class ImportantTooltip; } // namespace Ui namespace Dialogs::Stories { -class Thumbnail { -public: - [[nodiscard]] virtual QImage image(int size) = 0; - virtual void subscribeToUpdates(Fn callback) = 0; -}; - struct Element { uint64 id = 0; QString name; - std::shared_ptr thumbnail; + std::shared_ptr thumbnail; uint32 count : 15 = 0; uint32 unreadCount : 15 = 0; uint32 skipSmall : 1 = 0; diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 159a0a4de..859ab201e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -15,8 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_document.h" #include "data/data_media_types.h" -#include "dialogs/ui/dialogs_stories_content.h" -#include "dialogs/ui/dialogs_stories_list.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_item_components.h" @@ -30,6 +28,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/ripple_animation.h" #include "ui/text/text_utilities.h" #include "ui/widgets/tooltip.h" +#include "ui/dynamic_image.h" +#include "ui/dynamic_thumbnails.h" #include "ui/painter.h" #include "ui/round_rect.h" #include "styles/style_chat.h" @@ -446,7 +446,7 @@ PeerBubbleListPart::PeerBubbleListPart( peer->name(), kDefaultTextOptions, st::msgMinWidth), - .thumbnail = Dialogs::Stories::MakeUserpicThumbnail(peer), + .thumbnail = Ui::MakeUserpicThumbnail(peer), .link = peer->openLink(), .colorIndex = peer->colorIndex(), }); diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h index 534886fc7..1aae9340d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h @@ -15,11 +15,8 @@ struct GiveawayStart; struct GiveawayResults; } // namespace Data -namespace Dialogs::Stories { -class Thumbnail; -} // namespace Dialogs::Stories - namespace Ui { +class DynamicImage; class RippleAnimation; } // namespace Ui @@ -208,10 +205,9 @@ public: private: int layout(int x, int y, int available); - using Thumbnail = Dialogs::Stories::Thumbnail; struct Peer { Ui::Text::String name; - std::shared_ptr thumbnail; + std::shared_ptr thumbnail; QRect geometry; ClickHandlerPtr link; mutable std::unique_ptr ripple; diff --git a/Telegram/SourceFiles/history/view/media/history_view_similar_channels.cpp b/Telegram/SourceFiles/history/view/media/history_view_similar_channels.cpp index 919a76cb0..05a03df4d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_similar_channels.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_similar_channels.cpp @@ -14,8 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_premium_limits.h" #include "data/data_session.h" -#include "dialogs/ui/dialogs_stories_content.h" -#include "dialogs/ui/dialogs_stories_list.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" #include "history/history.h" @@ -30,6 +28,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_theme.h" #include "ui/effects/ripple_animation.h" #include "ui/text/text_utilities.h" +#include "ui/dynamic_image.h" +#include "ui/dynamic_thumbnails.h" #include "ui/painter.h" #include "window/window_session_controller.h" #include "styles/style_chat.h" @@ -373,7 +373,7 @@ void SimilarChannels::fillMoreThumbnails() const { if (similar.list.size() <= _channels.size() + i) { break; } - _moreThumbnails[i] = Dialogs::Stories::MakeUserpicThumbnail( + _moreThumbnails[i] = Ui::MakeUserpicThumbnail( similar.list[_channels.size() + i]); } } @@ -556,7 +556,7 @@ QSize SimilarChannels::countOptimalSize() { : channel->name()), kDefaultTextOptions, st::chatSimilarChannelPhoto), - .thumbnail = Dialogs::Stories::MakeUserpicThumbnail(channel), + .thumbnail = Ui::MakeUserpicThumbnail(channel), .more = uint32(moreCounter), .moreLocked = uint32((moreCounter && !premium) ? 1 : 0), }); diff --git a/Telegram/SourceFiles/history/view/media/history_view_similar_channels.h b/Telegram/SourceFiles/history/view/media/history_view_similar_channels.h index a7f30b2a7..138373914 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_similar_channels.h +++ b/Telegram/SourceFiles/history/view/media/history_view_similar_channels.h @@ -9,11 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media.h" -namespace Dialogs::Stories { -class Thumbnail; -} // namespace Dialogs::Stories - namespace Ui { +class DynamicImage; class RippleAnimation; } // namespace Ui @@ -58,11 +55,10 @@ public: bool consumeHorizontalScroll(QPoint position, int delta) override; private: - using Thumbnail = Dialogs::Stories::Thumbnail; struct Channel { QRect geometry; Ui::Text::String name; - std::shared_ptr thumbnail; + std::shared_ptr thumbnail; ClickHandlerPtr link; QString counter; mutable QRect counterRect; @@ -99,7 +95,7 @@ private: mutable uint32 _hasHeavyPart : 1 = 0; std::vector _channels; - mutable std::array, 2> _moreThumbnails; + mutable std::array, 2> _moreThumbnails; mutable ClickHandlerPtr _viewAllLink; mutable ClickHandlerPtr _toggleLink; diff --git a/Telegram/SourceFiles/history/view/media/history_view_story_mention.cpp b/Telegram/SourceFiles/history/view/media/history_view_story_mention.cpp index 2d9089f37..a2b2d65ba 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_story_mention.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_story_mention.cpp @@ -15,8 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_click_handler.h" #include "data/data_session.h" #include "data/data_stories.h" -#include "dialogs/ui/dialogs_stories_content.h" -#include "dialogs/ui/dialogs_stories_list.h" #include "editor/photo_editor_common.h" #include "editor/photo_editor_layer_widget.h" #include "history/history.h" @@ -31,6 +29,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/outline_segments.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" +#include "ui/dynamic_image.h" +#include "ui/dynamic_thumbnails.h" #include "ui/painter.h" #include "mainwidget.h" #include "apiwrap.h" @@ -102,12 +102,11 @@ void StoryMention::draw( const QRect &geometry) { const auto showStory = _story->forbidsForward() ? 0 : 1; if (!_thumbnail || _thumbnailFromStory != showStory) { - using namespace Dialogs::Stories; const auto item = _parent->data(); const auto history = item->history(); _thumbnail = showStory - ? MakeStoryThumbnail(_story) - : MakeUserpicThumbnail(item->out() + ? Ui::MakeStoryThumbnail(_story) + : Ui::MakeUserpicThumbnail(item->out() ? history->session().user() : history->peer); _thumbnailFromStory = showStory; diff --git a/Telegram/SourceFiles/history/view/media/history_view_story_mention.h b/Telegram/SourceFiles/history/view/media/history_view_story_mention.h index 376926f3b..601823460 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_story_mention.h +++ b/Telegram/SourceFiles/history/view/media/history_view_story_mention.h @@ -15,9 +15,9 @@ namespace Data { class Story; } // namespace Data -namespace Dialogs::Stories { -class Thumbnail; -} // namespace Dialogs::Stories +namespace Ui { +class DynamicImage; +} // namespace Ui namespace HistoryView { @@ -53,13 +53,11 @@ public: void unloadHeavyPart() override; private: - using Thumbnail = Dialogs::Stories::Thumbnail; - bool changeSubscribedTo(uint32 value); const not_null _parent; const not_null _story; - std::shared_ptr _thumbnail; + std::shared_ptr _thumbnail; QBrush _unreadBrush; uint32 _paletteVersion : 29 = 0; uint32 _thumbnailFromStory : 1 = 0; diff --git a/Telegram/SourceFiles/info/info_top_bar.cpp b/Telegram/SourceFiles/info/info_top_bar.cpp index 6c91d38ca..d9a422adb 100644 --- a/Telegram/SourceFiles/info/info_top_bar.cpp +++ b/Telegram/SourceFiles/info/info_top_bar.cpp @@ -7,9 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/info_top_bar.h" -#include -#include -#include "dialogs/ui/dialogs_stories_content.h" #include "dialogs/ui/dialogs_stories_list.h" #include "lang/lang_keys.h" #include "lang/lang_numbers_animation.h" diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp new file mode 100644 index 000000000..3cd8ba36a --- /dev/null +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp @@ -0,0 +1,322 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/dynamic_thumbnails.h" + +#include "data/data_changes.h" +#include "data/data_document.h" +#include "data/data_document_media.h" +#include "data/data_file_origin.h" +#include "data/data_peer.h" +#include "data/data_photo.h" +#include "data/data_photo_media.h" +#include "data/data_story.h" +#include "main/main_session.h" +#include "ui/dynamic_image.h" +#include "ui/painter.h" +#include "ui/userpic_view.h" + +namespace Ui { +namespace { + +class PeerUserpic final : public DynamicImage { +public: + explicit PeerUserpic(not_null peer); + + QImage image(int size) override; + void subscribeToUpdates(Fn callback) override; + +private: + struct Subscribed { + explicit Subscribed(Fn callback) + : callback(std::move(callback)) { + } + + Ui::PeerUserpicView view; + Fn callback; + InMemoryKey key; + rpl::lifetime photoLifetime; + rpl::lifetime downloadLifetime; + }; + + [[nodiscard]] bool waitingUserpicLoad() const; + void processNewPhoto(); + + const not_null _peer; + QImage _frame; + std::unique_ptr _subscribed; + +}; + +class StoryThumbnail : public DynamicImage { +public: + explicit StoryThumbnail(FullStoryId id); + virtual ~StoryThumbnail() = default; + + QImage image(int size) override; + void subscribeToUpdates(Fn callback) override; + +protected: + struct Thumb { + Image *image = nullptr; + bool blurred = false; + }; + [[nodiscard]] virtual Main::Session &session() = 0; + [[nodiscard]] virtual Thumb loaded(FullStoryId id) = 0; + virtual void clear() = 0; + +private: + const FullStoryId _id; + QImage _full; + rpl::lifetime _subscription; + QImage _prepared; + bool _blurred = false; + +}; + +class PhotoThumbnail final : public StoryThumbnail { +public: + PhotoThumbnail(not_null photo, FullStoryId id); + +private: + Main::Session &session() override; + Thumb loaded(FullStoryId id) override; + void clear() override; + + const not_null _photo; + std::shared_ptr _media; + +}; + +class VideoThumbnail final : public StoryThumbnail { +public: + VideoThumbnail(not_null video, FullStoryId id); + +private: + Main::Session &session() override; + Thumb loaded(FullStoryId id) override; + void clear() override; + + const not_null _video; + std::shared_ptr _media; + +}; + +class EmptyThumbnail final : public DynamicImage { +public: + QImage image(int size) override; + void subscribeToUpdates(Fn callback) override; + +private: + QImage _cached; + +}; + +PeerUserpic::PeerUserpic(not_null peer) +: _peer(peer) { +} + +QImage PeerUserpic::image(int size) { + Expects(_subscribed != nullptr); + + const auto good = (_frame.width() == size * _frame.devicePixelRatio()); + const auto key = _peer->userpicUniqueKey(_subscribed->view); + if (!good || (_subscribed->key != key && !waitingUserpicLoad())) { + const auto ratio = style::DevicePixelRatio(); + _subscribed->key = key; + _frame = QImage( + QSize(size, size) * ratio, + QImage::Format_ARGB32_Premultiplied); + _frame.setDevicePixelRatio(ratio); + _frame.fill(Qt::transparent); + + auto p = Painter(&_frame); + _peer->paintUserpic(p, _subscribed->view, 0, 0, size); + } + return _frame; +} + +bool PeerUserpic::waitingUserpicLoad() const { + return _peer->hasUserpic() && _peer->useEmptyUserpic(_subscribed->view); +} + +void PeerUserpic::subscribeToUpdates(Fn callback) { + if (!callback) { + _subscribed = nullptr; + return; + } + _subscribed = std::make_unique(std::move(callback)); + + _peer->session().changes().peerUpdates( + _peer, + Data::PeerUpdate::Flag::Photo + ) | rpl::start_with_next([=] { + _subscribed->callback(); + processNewPhoto(); + }, _subscribed->photoLifetime); + + processNewPhoto(); +} + +void PeerUserpic::processNewPhoto() { + Expects(_subscribed != nullptr); + + if (!waitingUserpicLoad()) { + _subscribed->downloadLifetime.destroy(); + return; + } + _peer->session().downloaderTaskFinished( + ) | rpl::filter([=] { + return !waitingUserpicLoad(); + }) | rpl::start_with_next([=] { + _subscribed->callback(); + _subscribed->downloadLifetime.destroy(); + }, _subscribed->downloadLifetime); +} + +StoryThumbnail::StoryThumbnail(FullStoryId id) +: _id(id) { +} + +QImage StoryThumbnail::image(int size) { + const auto ratio = style::DevicePixelRatio(); + if (_prepared.width() != size * ratio) { + if (_full.isNull()) { + _prepared = QImage( + QSize(size, size) * ratio, + QImage::Format_ARGB32_Premultiplied); + _prepared.fill(Qt::black); + } else { + const auto width = _full.width(); + const auto skip = std::max((_full.height() - width) / 2, 0); + _prepared = _full.copy(0, skip, width, width).scaled( + QSize(size, size) * ratio, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + } + _prepared = Images::Circle(std::move(_prepared)); + _prepared.setDevicePixelRatio(ratio); + } + return _prepared; +} + +void StoryThumbnail::subscribeToUpdates(Fn callback) { + _subscription.destroy(); + if (!callback) { + clear(); + return; + } else if (!_full.isNull() && !_blurred) { + return; + } + const auto thumbnail = loaded(_id); + if (const auto image = thumbnail.image) { + _full = image->original(); + } + _blurred = thumbnail.blurred; + if (!_blurred) { + _prepared = QImage(); + } else { + _subscription = session().downloaderTaskFinished( + ) | rpl::filter([=] { + const auto thumbnail = loaded(_id); + if (!thumbnail.blurred) { + _full = thumbnail.image->original(); + _prepared = QImage(); + _blurred = false; + return true; + } + return false; + }) | rpl::take(1) | rpl::start_with_next(callback); + } +} + +PhotoThumbnail::PhotoThumbnail(not_null photo, FullStoryId id) +: StoryThumbnail(id) +, _photo(photo) { +} + +Main::Session &PhotoThumbnail::session() { + return _photo->session(); +} + +StoryThumbnail::Thumb PhotoThumbnail::loaded(FullStoryId id) { + if (!_media) { + _media = _photo->createMediaView(); + _media->wanted(Data::PhotoSize::Small, id); + } + if (const auto small = _media->image(Data::PhotoSize::Small)) { + return { .image = small }; + } + return { .image = _media->thumbnailInline(), .blurred = true }; +} + +void PhotoThumbnail::clear() { + _media = nullptr; +} + +VideoThumbnail::VideoThumbnail( + not_null video, + FullStoryId id) +: StoryThumbnail(id) +, _video(video) { +} + +Main::Session &VideoThumbnail::session() { + return _video->session(); +} + +StoryThumbnail::Thumb VideoThumbnail::loaded(FullStoryId id) { + if (!_media) { + _media = _video->createMediaView(); + _media->thumbnailWanted(id); + } + if (const auto small = _media->thumbnail()) { + return { .image = small }; + } + return { .image = _media->thumbnailInline(), .blurred = true }; +} + +void VideoThumbnail::clear() { + _media = nullptr; +} + +QImage EmptyThumbnail::image(int size) { + const auto ratio = style::DevicePixelRatio(); + if (_cached.width() != size * ratio) { + _cached = QImage( + QSize(size, size) * ratio, + QImage::Format_ARGB32_Premultiplied); + _cached.fill(Qt::black); + _cached.setDevicePixelRatio(ratio); + } + return _cached; +} + +void EmptyThumbnail::subscribeToUpdates(Fn callback) { +} + +} // namespace + +std::shared_ptr MakeUserpicThumbnail( + not_null peer) { + return std::make_shared(peer); +} + +std::shared_ptr MakeStoryThumbnail( + not_null story) { + using Result = std::shared_ptr; + const auto id = story->fullId(); + return v::match(story->media().data, [](v::null_t) -> Result { + return std::make_shared(); + }, [&](not_null photo) -> Result { + return std::make_shared(photo, id); + }, [&](not_null video) -> Result { + return std::make_shared(video, id); + }); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.h b/Telegram/SourceFiles/ui/dynamic_thumbnails.h new file mode 100644 index 000000000..a3a95111f --- /dev/null +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.h @@ -0,0 +1,25 @@ +/* +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 + +class PeerData; + +namespace Data { +class Story; +} // namespace Data + +namespace Ui { + +class DynamicImage; + +[[nodiscard]] std::shared_ptr MakeUserpicThumbnail( + not_null peer); +[[nodiscard]] std::shared_ptr MakeStoryThumbnail( + not_null story); + +} // namespace Ui diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 7328e2786..d42475113 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 7328e2786248c673e3599695a56989d9c1062303 +Subproject commit d4247511355a666903e9a57d821b1eb58884aade From 38e082422a700d9b90094ac0e021179ec777b645 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 12 Feb 2024 15:34:48 +0400 Subject: [PATCH 36/73] Show story sender / repost userpic under story source. --- Telegram/SourceFiles/data/data_story.cpp | 14 +++++++ Telegram/SourceFiles/data/data_story.h | 3 ++ .../data/stickers/data_custom_emoji.cpp | 2 +- .../stories/media_stories_controller.cpp | 1 + .../media/stories/media_stories_header.cpp | 42 ++++++++++++++----- .../media/stories/media_stories_header.h | 1 + .../SourceFiles/media/view/media_view.style | 3 +- 7 files changed, 53 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index 7631892a3..d4b12de6a 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -164,6 +164,15 @@ using UpdateFlag = StoryUpdate::Flag; return false; } +[[nodiscard]] PeerData *FromPeer( + not_null owner, + const MTPDstoryItem &data) { + if (const auto from = data.vfrom_id()) { + return owner->peer(peerFromMTP(*from)); + } + return nullptr; +} + } // namespace class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask { @@ -278,6 +287,7 @@ Story::Story( , _repostSourcePeer(RepostSourcePeer(&peer->owner(), data)) , _repostSourceName(RepostSourceName(data)) , _repostSourceId(RepostSourceId(data)) +, _fromPeer(FromPeer(&peer->owner(), data)) , _date(data.vdate().v) , _expires(data.vexpire_date().v) , _repostModified(RepostModified(data)) { @@ -890,6 +900,10 @@ StoryId Story::repostSourceId() const { return _repostSourceId; } +PeerData *Story::fromPeer() const { + return _fromPeer; +} + StoryPreload::StoryPreload(not_null story, Fn done) : _story(story) , _done(std::move(done)) { diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index 4fb4796b2..41da0239e 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -208,6 +208,8 @@ public: [[nodiscard]] QString repostSourceName() const; [[nodiscard]] StoryId repostSourceId() const; + [[nodiscard]] PeerData *fromPeer() const; + private: struct ViewsCounts { int views = 0; @@ -234,6 +236,7 @@ private: PeerData * const _repostSourcePeer = nullptr; const QString _repostSourceName; const StoryId _repostSourceId = 0; + PeerData * const _fromPeer = nullptr; Data::ReactionId _sentReactionId; StoryMedia _media; TextWithEntities _caption; diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 1e225afc5..0ce9ccf78 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -538,7 +538,7 @@ std::unique_ptr CustomEmojiManager::create( return internal(data); } else if (data.startsWith(UserpicEmojiPrefix())) { const auto ratio = style::DevicePixelRatio(); - const auto size = FrameSizeFromTag(tag, sizeOverride) / ratio; + const auto size = EmojiSizeFromTag(tag) / ratio; return userpic(data, std::move(update), size); } const auto parsed = ParseCustomEmojiData(data); diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 7814b71d2..e9170bbb1 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -883,6 +883,7 @@ void Controller::show( const auto document = story->document(); _header->show({ .peer = peer, + .fromPeer = story->fromPeer(), .repostPeer = _repostView ? _repostView->fromPeer() : nullptr, .repostFrom = _repostView ? _repostView->fromName() : nullptr, .date = story->date(), diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.cpp b/Telegram/SourceFiles/media/stories/media_stories_header.cpp index 8b14a984b..b247acf81 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_header.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_header.cpp @@ -255,21 +255,37 @@ struct MadePrivacyBadge { result.text.append( QString::fromUtf8(" \xE2\x80\xA2 ") + tr::lng_edited(tr::now)); } - if (!data.repostFrom.isEmpty()) { + if (data.fromPeer || !data.repostFrom.isEmpty()) { result.text = QString::fromUtf8("\xE2\x80\xA2 ") + result.text; } return result; } +[[nodiscard]] TextWithEntities FromNameValue(not_null from) { + auto result = Ui::Text::SingleCustomEmoji( + from->owner().customEmojiManager().peerUserpicEmojiData( + from, + st::storiesRepostUserpicPadding)); + result.append(from->name()); + return Ui::Text::Link(result); +} + [[nodiscard]] TextWithEntities RepostNameValue( not_null owner, + PeerData *peer, QString name) { - const auto result = Ui::Text::SingleCustomEmoji( + auto result = Ui::Text::SingleCustomEmoji( owner->customEmojiManager().registerInternalEmoji( st::storiesRepostIcon, - st::storiesRepostIconPadding) - ).append(name); + st::storiesRepostIconPadding)); + if (peer) { + result.append(Ui::Text::SingleCustomEmoji( + owner->customEmojiManager().peerUserpicEmojiData( + peer, + st::storiesRepostUserpicPadding))); + } + result.append(name); return Ui::Text::Link(result); } @@ -371,24 +387,28 @@ void Header::show(HeaderData data) { _date->widthValue( ) | rpl::start_with_next(updateInfoGeometry, _date->lifetime()); - if (data.repostFrom.isEmpty()) { + if (!data.fromPeer && data.repostFrom.isEmpty()) { _repost = nullptr; } else { _repost = std::make_unique( _widget.get(), st::storiesHeaderDate); - const auto repostName = RepostNameValue( - &data.peer->owner(), - data.repostFrom); + const auto prefixName = data.fromPeer + ? FromNameValue(data.fromPeer) + : RepostNameValue( + &data.peer->owner(), + data.repostPeer, + data.repostFrom); + const auto prefix = data.fromPeer ? data.fromPeer : data.repostPeer; _repost->setMarkedText( - data.repostPeer ? Ui::Text::Link(repostName) : repostName, + (prefix ? Ui::Text::Link(prefixName) : prefixName), Core::MarkedTextContext{ .session = &data.peer->session(), .customEmojiRepaint = [=] { _repost->update(); }, }); - if (const auto peer = data.repostPeer) { + if (prefix) { _repost->setClickHandlerFilter([=](const auto &...) { - _controller->uiShow()->show(PrepareShortInfoBox(peer)); + _controller->uiShow()->show(PrepareShortInfoBox(prefix)); return false; }); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.h b/Telegram/SourceFiles/media/stories/media_stories_header.h index cf360cc90..f02c4ff37 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_header.h +++ b/Telegram/SourceFiles/media/stories/media_stories_header.h @@ -32,6 +32,7 @@ enum class PauseState; struct HeaderData { not_null peer; + PeerData *fromPeer = nullptr; PeerData *repostPeer = nullptr; QString repostFrom; TimeId date = 0; diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index d144eba5c..5da7f93a8 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -1046,4 +1046,5 @@ storiesRepostSimpleStyle: QuoteStyle(defaultQuoteStyle) { radius: 10px; } storiesRepostIcon: icon {{ "mediaview/mini_repost", windowFg }}; -storiesRepostIconPadding: margins(0px, 4px, 4px, 0px); +storiesRepostIconPadding: margins(0px, 4px, 2px, 0px); +storiesRepostUserpicPadding: margins(0px, 1px, 4px, 0px); From 137155afd8901e2bff6e3746bfe08b1ce17cd10c Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 16 Feb 2024 11:26:42 +0400 Subject: [PATCH 37/73] Use round thumbnail in forum stories. --- .../SourceFiles/dialogs/dialogs_widget.cpp | 15 +++++------ .../dialogs/ui/dialogs_stories_content.cpp | 2 +- .../SourceFiles/ui/dynamic_thumbnails.cpp | 27 ++++++++++++++----- Telegram/SourceFiles/ui/dynamic_thumbnails.h | 3 ++- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 1a701a7b6..6ce80f8a9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -528,6 +528,13 @@ void Widget::chosenRow(const ChosenRow &row) { topic, row.message.fullId.msg, Window::SectionShow::Way::ClearStack); + } else if (history + && row.userpicClick + && (row.message.fullId.msg == ShowAtUnreadMsgId) + && history->peer->hasActiveStories() + && !history->peer->isSelf()) { + controller()->openPeerStories(history->peer->id); + return; } else if (history && history->isForum() && !row.message.fullId @@ -558,14 +565,6 @@ void Widget::chosenRow(const ChosenRow &row) { return; } else if (history) { const auto peer = history->peer; - if (row.message.fullId.msg == ShowAtUnreadMsgId) { - if (row.userpicClick - && peer->hasActiveStories() - && !peer->isSelf()) { - controller()->openPeerStories(peer->id); - return; - } - } const auto showAtMsgId = controller()->uniqueChatsInSearchResults() ? ShowAtUnreadMsgId : row.message.fullId.msg; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp index bf60a2488..f676861b0 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp @@ -66,7 +66,7 @@ Content State::next() { if (const auto i = _userpics.find(peer); i != end(_userpics)) { userpic = i->second; } else { - userpic = Ui::MakeUserpicThumbnail(peer); + userpic = Ui::MakeUserpicThumbnail(peer, true); _userpics.emplace(peer, userpic); } result.elements.push_back({ diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp index 3cd8ba36a..d8857f349 100644 --- a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp @@ -25,7 +25,7 @@ namespace { class PeerUserpic final : public DynamicImage { public: - explicit PeerUserpic(not_null peer); + PeerUserpic(not_null peer, bool forceRound); QImage image(int size) override; void subscribeToUpdates(Fn callback) override; @@ -33,7 +33,7 @@ public: private: struct Subscribed { explicit Subscribed(Fn callback) - : callback(std::move(callback)) { + : callback(std::move(callback)) { } Ui::PeerUserpicView view; @@ -49,6 +49,7 @@ private: const not_null _peer; QImage _frame; std::unique_ptr _subscribed; + bool _forceRound = false; }; @@ -116,8 +117,9 @@ private: }; -PeerUserpic::PeerUserpic(not_null peer) -: _peer(peer) { +PeerUserpic::PeerUserpic(not_null peer, bool forceRound) +: _peer(peer) +, _forceRound(forceRound) { } QImage PeerUserpic::image(int size) { @@ -135,7 +137,17 @@ QImage PeerUserpic::image(int size) { _frame.fill(Qt::transparent); auto p = Painter(&_frame); - _peer->paintUserpic(p, _subscribed->view, 0, 0, size); + auto &view = _subscribed->view; + if (!_forceRound) { + _peer->paintUserpic(p, view, 0, 0, size); + } else if (const auto cloud = _peer->userpicCloudImage(view)) { + Ui::ValidateUserpicCache(view, cloud, nullptr, size, false); + p.drawImage(QRect(0, 0, size, size), view.cached); + } else { + const auto r = size / 2.; + const auto empty = _peer->generateUserpicImage(view, size, r); + p.drawImage(QRect(0, 0, size, size), empty); + } } return _frame; } @@ -302,8 +314,9 @@ void EmptyThumbnail::subscribeToUpdates(Fn callback) { } // namespace std::shared_ptr MakeUserpicThumbnail( - not_null peer) { - return std::make_shared(peer); + not_null peer, + bool forceRound) { + return std::make_shared(peer, forceRound); } std::shared_ptr MakeStoryThumbnail( diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.h b/Telegram/SourceFiles/ui/dynamic_thumbnails.h index a3a95111f..cbf3cb235 100644 --- a/Telegram/SourceFiles/ui/dynamic_thumbnails.h +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.h @@ -18,7 +18,8 @@ namespace Ui { class DynamicImage; [[nodiscard]] std::shared_ptr MakeUserpicThumbnail( - not_null peer); + not_null peer, + bool forceRound = false); [[nodiscard]] std::shared_ptr MakeStoryThumbnail( not_null story); From 9f7ee3cafdc467da75286d9519bbdb75b7ab1e09 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 16 Feb 2024 12:13:41 +0400 Subject: [PATCH 38/73] Correctly check webview init success. Also correctly init recreated webview bottom bar. Fixes #27481, fixes #27479. --- .../ui/chat/attach/attach_bot_webview.cpp | 61 +++++++++++-------- .../ui/chat/attach/attach_bot_webview.h | 2 + 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index ab84a81cd..ed57fdf2b 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -494,6 +494,7 @@ bool Panel::showWebview( const QString &url, const Webview::ThemeParams ¶ms, rpl::producer bottomText) { + _bottomText = std::move(bottomText); if (!_webview && !createWebview(params)) { return false; } @@ -503,24 +504,6 @@ bool Panel::showWebview( updateThemeParams(params); _webview->window.navigate(url); _widget->setBackAllowed(allowBack); - if (bottomText) { - const auto &padding = st::paymentsPanelPadding; - const auto label = CreateChild( - _webviewBottom.get(), - std::move(bottomText), - st::paymentsWebviewBottom); - const auto height = padding.top() - + label->heightNoMargins() - + padding.bottom(); - rpl::combine( - _webviewBottom->widthValue(), - label->widthValue() - ) | rpl::start_with_next([=](int outerWidth, int width) { - label->move((outerWidth - width) / 2, padding.top()); - }, label->lifetime()); - label->show(); - _webviewBottom->resize(_webviewBottom->width(), height); - } _widget->setMenuAllowed([=](const Ui::Menu::MenuCallback &callback) { if (_hasSettingsButton) { callback(tr::lng_bot_settings(tr::now), [=] { @@ -533,7 +516,7 @@ bool Panel::showWebview( }, &st::menuIconLeave); } callback(tr::lng_bot_reload_page(tr::now), [=] { - if (_webview) { + if (_webview && _webview->window.widget()) { _webview->window.reload(); } else if (const auto params = _delegate->botThemeParams() ; createWebview(params)) { @@ -562,16 +545,28 @@ bool Panel::showWebview( return true; } -bool Panel::createWebview(const Webview::ThemeParams ¶ms) { - auto outer = base::make_unique_q(_widget.get()); - const auto container = outer.get(); - _widget->showInner(std::move(outer)); - _webviewParent = container; - +void Panel::createWebviewBottom() { _webviewBottom = std::make_unique(_widget.get()); const auto bottom = _webviewBottom.get(); bottom->show(); + const auto &padding = st::paymentsPanelPadding; + const auto label = CreateChild( + _webviewBottom.get(), + _bottomText.value(), + st::paymentsWebviewBottom); + const auto height = padding.top() + + label->heightNoMargins() + + padding.bottom(); + rpl::combine( + _webviewBottom->widthValue(), + label->widthValue() + ) | rpl::start_with_next([=](int outerWidth, int width) { + label->move((outerWidth - width) / 2, padding.top()); + }, label->lifetime()); + label->show(); + _webviewBottom->resize(_webviewBottom->width(), height); + bottom->heightValue( ) | rpl::start_with_next([=](int height) { const auto inner = _widget->innerGeometry(); @@ -579,11 +574,22 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) { height = _mainButton->height(); } bottom->move(inner.x(), inner.y() + inner.height() - height); - container->setFixedSize(inner.width(), inner.height() - height); + if (const auto container = _webviewParent.data()) { + container->setFixedSize(inner.width(), inner.height() - height); + } bottom->resizeToWidth(inner.width()); }, bottom->lifetime()); - container->show(); +} +bool Panel::createWebview(const Webview::ThemeParams ¶ms) { + auto outer = base::make_unique_q(_widget.get()); + const auto container = outer.get(); + _widget->showInner(std::move(outer)); + _webviewParent = container; + + createWebviewBottom(); + + container->show(); _webview = std::make_unique( container, Webview::WindowConfig{ @@ -592,6 +598,7 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) { }); const auto raw = &_webview->window; + const auto bottom = _webviewBottom.get(); QObject::connect(container, &QObject::destroyed, [=] { if (_webview && &_webview->window == raw) { base::take(_webview); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h index 20a9a4f72..9c4be46d0 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h @@ -106,6 +106,7 @@ private: struct WebviewWithLifetime; bool createWebview(const Webview::ThemeParams ¶ms); + void createWebviewBottom(); void showWebviewProgress(); void hideWebviewProgress(); void setTitle(rpl::producer title); @@ -150,6 +151,7 @@ private: std::unique_ptr _widget; std::unique_ptr _webview; std::unique_ptr _webviewBottom; + rpl::variable _bottomText; QPointer _webviewParent; std::unique_ptr